From f84a7b65e7b2be743865a5d237a24a229939f8ba Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:27:22 +0200 Subject: [PATCH 001/296] fix(client): parse env vars for debug and tracing enabled case insensitive (#1279) --- langfuse/__init__.py | 4 ++-- langfuse/_client/client.py | 6 ++++-- tests/test_otel.py | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/langfuse/__init__.py b/langfuse/__init__.py index a61325071..5d8da4cc2 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -1,12 +1,12 @@ """.. include:: ../README.md""" +from ._client import client as _client_module from ._client.attributes import LangfuseOtelSpanAttributes from ._client.get_client import get_client -from ._client import client as _client from ._client.observe import observe from ._client.span import LangfuseEvent, LangfuseGeneration, LangfuseSpan -Langfuse = _client.Langfuse +Langfuse = _client_module.Langfuse __all__ = [ "Langfuse", diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 0f80f1816..d205e5d51 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -187,14 +187,16 @@ def __init__( self._tracing_enabled = ( tracing_enabled - and os.environ.get(LANGFUSE_TRACING_ENABLED, "True") != "False" + and os.environ.get(LANGFUSE_TRACING_ENABLED, "true").lower() != "false" ) if not self._tracing_enabled: langfuse_logger.info( "Configuration: Langfuse tracing is explicitly disabled. No data will be sent to the Langfuse API." ) - debug = debug if debug else (os.getenv(LANGFUSE_DEBUG, "False") == "True") + debug = ( + debug if debug else (os.getenv(LANGFUSE_DEBUG, "false").lower() == "true") + ) if debug: logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" diff --git a/tests/test_otel.py b/tests/test_otel.py index 3e52b10fc..dfa298161 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -95,7 +95,8 @@ def mock_init(self, **kwargs): ) monkeypatch.setattr( - "langfuse._client.span_processor.LangfuseSpanProcessor.__init__", mock_init + "langfuse._client.span_processor.LangfuseSpanProcessor.__init__", + mock_init, ) @pytest.fixture From fc44bec1294ce7ef08c4c93a9e53c7c0e98da3b5 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:47:34 +0200 Subject: [PATCH 002/296] feat(decorator): require langfuse_public_key only in top most decorated func (#1281) --- langfuse/_client/get_client.py | 34 +- langfuse/_client/observe.py | 224 +++++----- tests/test_decorators.py | 607 +++++++++++++++++++++++++++- tests/test_langchain_integration.py | 163 ++++++-- 4 files changed, 881 insertions(+), 147 deletions(-) diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index 98a64fbfe..ff619095e 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -1,9 +1,37 @@ -from typing import Optional +from contextlib import contextmanager +from contextvars import ContextVar +from typing import Iterator, Optional from langfuse._client.client import Langfuse from langfuse._client.resource_manager import LangfuseResourceManager from langfuse.logger import langfuse_logger +# Context variable to track the current langfuse_public_key in execution context +_current_public_key: ContextVar[Optional[str]] = ContextVar( + "langfuse_public_key", default=None +) + + +@contextmanager +def _set_current_public_key(public_key: Optional[str]) -> Iterator[None]: + """Context manager to set and restore the current public key in execution context. + + Args: + public_key: The public key to set in context. If None, context is not modified. + + Yields: + None + """ + if public_key is None: + yield # Don't modify context if no key provided + return + + token = _current_public_key.set(public_key) + try: + yield + finally: + _current_public_key.reset(token) + def get_client(*, public_key: Optional[str] = None) -> Langfuse: """Get or create a Langfuse client instance. @@ -49,6 +77,10 @@ def get_client(*, public_key: Optional[str] = None) -> Langfuse: with LangfuseResourceManager._lock: active_instances = LangfuseResourceManager._instances + # If no explicit public_key provided, check execution context + if not public_key: + public_key = _current_public_key.get(None) + if not public_key: if len(active_instances) == 0: # No clients initialized yet, create default instance diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index 0e68bc965..0fef2b5dd 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -25,7 +25,7 @@ from langfuse._client.environment_variables import ( LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, ) -from langfuse._client.get_client import get_client +from langfuse._client.get_client import _set_current_public_key, get_client from langfuse._client.span import LangfuseGeneration, LangfuseSpan from langfuse.types import TraceContext @@ -231,72 +231,75 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: else None ) public_key = cast(str, kwargs.pop("langfuse_public_key", None)) - langfuse_client = get_client(public_key=public_key) - context_manager: Optional[ - Union[ - _AgnosticContextManager[LangfuseGeneration], - _AgnosticContextManager[LangfuseSpan], - ] - ] = ( - ( - langfuse_client.start_as_current_generation( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early - ) - if as_type == "generation" - else langfuse_client.start_as_current_span( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early + + # Set public key in execution context for nested decorated functions + with _set_current_public_key(public_key): + langfuse_client = get_client(public_key=public_key) + context_manager: Optional[ + Union[ + _AgnosticContextManager[LangfuseGeneration], + _AgnosticContextManager[LangfuseSpan], + ] + ] = ( + ( + langfuse_client.start_as_current_generation( + name=final_name, + trace_context=trace_context, + input=input, + end_on_exit=False, # when returning a generator, closing on exit would be to early + ) + if as_type == "generation" + else langfuse_client.start_as_current_span( + name=final_name, + trace_context=trace_context, + input=input, + end_on_exit=False, # when returning a generator, closing on exit would be to early + ) ) + if langfuse_client + else None ) - if langfuse_client - else None - ) - if context_manager is None: - return await func(*args, **kwargs) + if context_manager is None: + return await func(*args, **kwargs) - with context_manager as langfuse_span_or_generation: - is_return_type_generator = False + with context_manager as langfuse_span_or_generation: + is_return_type_generator = False - try: - result = await func(*args, **kwargs) + try: + result = await func(*args, **kwargs) - if capture_output is True: - if inspect.isgenerator(result): - is_return_type_generator = True + if capture_output is True: + if inspect.isgenerator(result): + is_return_type_generator = True - return self._wrap_sync_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) + return self._wrap_sync_generator_result( + langfuse_span_or_generation, + result, + transform_to_string, + ) - if inspect.isasyncgen(result): - is_return_type_generator = True + if inspect.isasyncgen(result): + is_return_type_generator = True - return self._wrap_async_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) + return self._wrap_async_generator_result( + langfuse_span_or_generation, + result, + transform_to_string, + ) - langfuse_span_or_generation.update(output=result) + langfuse_span_or_generation.update(output=result) - return result - except Exception as e: - langfuse_span_or_generation.update( - level="ERROR", status_message=str(e) - ) + return result + except Exception as e: + langfuse_span_or_generation.update( + level="ERROR", status_message=str(e) + ) - raise e - finally: - if not is_return_type_generator: - langfuse_span_or_generation.end() + raise e + finally: + if not is_return_type_generator: + langfuse_span_or_generation.end() return cast(F, async_wrapper) @@ -333,72 +336,75 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: else None ) public_key = kwargs.pop("langfuse_public_key", None) - langfuse_client = get_client(public_key=public_key) - context_manager: Optional[ - Union[ - _AgnosticContextManager[LangfuseGeneration], - _AgnosticContextManager[LangfuseSpan], - ] - ] = ( - ( - langfuse_client.start_as_current_generation( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early - ) - if as_type == "generation" - else langfuse_client.start_as_current_span( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early + + # Set public key in execution context for nested decorated functions + with _set_current_public_key(public_key): + langfuse_client = get_client(public_key=public_key) + context_manager: Optional[ + Union[ + _AgnosticContextManager[LangfuseGeneration], + _AgnosticContextManager[LangfuseSpan], + ] + ] = ( + ( + langfuse_client.start_as_current_generation( + name=final_name, + trace_context=trace_context, + input=input, + end_on_exit=False, # when returning a generator, closing on exit would be to early + ) + if as_type == "generation" + else langfuse_client.start_as_current_span( + name=final_name, + trace_context=trace_context, + input=input, + end_on_exit=False, # when returning a generator, closing on exit would be to early + ) ) + if langfuse_client + else None ) - if langfuse_client - else None - ) - if context_manager is None: - return func(*args, **kwargs) + if context_manager is None: + return func(*args, **kwargs) - with context_manager as langfuse_span_or_generation: - is_return_type_generator = False + with context_manager as langfuse_span_or_generation: + is_return_type_generator = False - try: - result = func(*args, **kwargs) + try: + result = func(*args, **kwargs) - if capture_output is True: - if inspect.isgenerator(result): - is_return_type_generator = True + if capture_output is True: + if inspect.isgenerator(result): + is_return_type_generator = True - return self._wrap_sync_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) + return self._wrap_sync_generator_result( + langfuse_span_or_generation, + result, + transform_to_string, + ) - if inspect.isasyncgen(result): - is_return_type_generator = True + if inspect.isasyncgen(result): + is_return_type_generator = True - return self._wrap_async_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) + return self._wrap_async_generator_result( + langfuse_span_or_generation, + result, + transform_to_string, + ) - langfuse_span_or_generation.update(output=result) + langfuse_span_or_generation.update(output=result) - return result - except Exception as e: - langfuse_span_or_generation.update( - level="ERROR", status_message=str(e) - ) + return result + except Exception as e: + langfuse_span_or_generation.update( + level="ERROR", status_message=str(e) + ) - raise e - finally: - if not is_return_type_generator: - langfuse_span_or_generation.end() + raise e + finally: + if not is_return_type_generator: + langfuse_span_or_generation.end() return cast(F, sync_wrapper) diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 47bc3b015..6598bac55 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,4 +1,5 @@ import asyncio +import os from collections import defaultdict from concurrent.futures import ThreadPoolExecutor from time import sleep @@ -8,7 +9,9 @@ from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI -from langfuse import get_client, observe +from langfuse import Langfuse, get_client, observe +from langfuse._client.environment_variables import LANGFUSE_PUBLIC_KEY +from langfuse._client.resource_manager import LangfuseResourceManager from langfuse.langchain import CallbackHandler from langfuse.media import LangfuseMedia from tests.utils import get_api @@ -20,6 +23,13 @@ mock_kwargs = {"a": 1, "b": 2, "c": 3} +def removeMockResourceManagerInstances(): + with LangfuseResourceManager._lock: + for public_key in list(LangfuseResourceManager._instances.keys()): + if public_key != os.getenv(LANGFUSE_PUBLIC_KEY): + LangfuseResourceManager._instances.pop(public_key) + + def test_nested_observations(): mock_name = "test_nested_observations" langfuse = get_client() @@ -1081,3 +1091,598 @@ def main(): assert trace_data.metadata["key2"] == "value2" assert trace_data.tags == ["tag1", "tag2"] + + +# Multi-project context propagation tests +def test_multiproject_context_propagation_basic(): + """Test that nested decorated functions inherit langfuse_public_key from parent in multi-project setup""" + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_context_propagation_basic" + # Use known public key from environment + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + # In multi-project setup, must specify which client to use + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(as_type="generation", capture_output=False) + def level_3_function(): + # This function should inherit the public key from level_1_function + # and NOT need langfuse_public_key parameter + langfuse_client = get_client() + langfuse_client.update_current_generation(metadata={"level": "3"}) + langfuse_client.update_current_trace(name=mock_name) + return "level_3" + + @observe() + def level_2_function(): + # This function should also inherit the public key + level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2"}) + return "level_2" + + @observe() + def level_1_function(*args, **kwargs): + # Only this top-level function receives langfuse_public_key + level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1"}) + return "level_1" + + result = level_1_function( + *mock_args, + **mock_kwargs, + langfuse_trace_id=mock_trace_id, + langfuse_public_key=env_public_key, # Only provided to top-level function + ) + + # Use the correct client for flushing + client1.flush() + + assert result == "level_1" + + # Verify trace was created properly + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 3 + assert trace_data.name == mock_name + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +def test_multiproject_context_propagation_deep_nesting(): + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_context_propagation_deep_nesting" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(as_type="generation") + def level_4_function(): + langfuse_client = get_client() + langfuse_client.update_current_generation(metadata={"level": "4"}) + return "level_4" + + @observe() + def level_3_function(): + result = level_4_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "3"}) + return result + + @observe() + def level_2_function(): + result = level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2"}) + return result + + @observe() + def level_1_function(*args, **kwargs): + langfuse_client = get_client() + langfuse_client.update_current_trace(name=mock_name) + result = level_2_function() + langfuse_client.update_current_span(metadata={"level": "1"}) + return result + + result = level_1_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key + ) + client1.flush() + + assert result == "level_4" + + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 4 + assert trace_data.name == mock_name + + # Verify all levels were captured + levels = [ + str(obs.metadata.get("level")) + for obs in trace_data.observations + if obs.metadata + ] + assert set(levels) == {"1", "2", "3", "4"} + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +def test_multiproject_context_propagation_override(): + # Initialize two separate Langfuse instances + client1 = Langfuse() # Reads from environment + client2 = Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_context_propagation_override" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + primary_public_key = env_public_key + override_public_key = "pk-test-project2" + + @observe(as_type="generation") + def level_3_function(): + # This function explicitly overrides the inherited public key + langfuse_client = get_client(public_key=override_public_key) + langfuse_client.update_current_generation(metadata={"used_override": "true"}) + return "level_3" + + @observe() + def level_2_function(): + # This function should use the overridden key when calling level_3 + level_3_function(langfuse_public_key=override_public_key) + langfuse_client = get_client(public_key=primary_public_key) + langfuse_client.update_current_span(metadata={"level": "2"}) + return "level_2" + + @observe() + def level_1_function(*args, **kwargs): + langfuse_client = get_client(public_key=primary_public_key) + langfuse_client.update_current_trace(name=mock_name) + level_2_function() + return "level_1" + + result = level_1_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=primary_public_key + ) + client1.flush() + client2.flush() + + assert result == "level_1" + + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + assert trace_data.name == mock_name + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +def test_multiproject_context_propagation_no_public_key(): + # Initialize two separate Langfuse instances + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_context_propagation_no_public_key" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(as_type="generation") + def level_3_function(): + # Should use default client since no public key provided + langfuse_client = get_client() + langfuse_client.update_current_generation(metadata={"level": "3"}) + return "level_3" + + @observe() + def level_2_function(): + result = level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2"}) + return result + + @observe() + def level_1_function(*args, **kwargs): + langfuse_client = get_client() + langfuse_client.update_current_trace(name=mock_name) + result = level_2_function() + langfuse_client.update_current_span(metadata={"level": "1"}) + return result + + # No langfuse_public_key provided - should use default client + result = level_1_function(langfuse_trace_id=mock_trace_id) + client1.flush() + + assert result == "level_3" + + # Should skip tracing entirely in multi-project setup without public key + # This is expected behavior to prevent cross-project data leakage + try: + trace_data = get_api().trace.get(mock_trace_id) + # If trace is found, it should have no observations (tracing was skipped) + assert len(trace_data.observations) == 0 + except Exception: + # Trace not found is also expected - tracing was completely disabled + pass + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +@pytest.mark.asyncio +async def test_multiproject_async_context_propagation_basic(): + """Test that nested async decorated functions inherit langfuse_public_key from parent in multi-project setup""" + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_async_context_propagation_basic" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(as_type="generation", capture_output=False) + async def async_level_3_function(): + # This function should inherit the public key from level_1_function + # and NOT need langfuse_public_key parameter + await asyncio.sleep(0.01) # Simulate async work + langfuse_client = get_client() + langfuse_client.update_current_generation( + metadata={"level": "3", "async": True} + ) + langfuse_client.update_current_trace(name=mock_name) + return "async_level_3" + + @observe() + async def async_level_2_function(): + # This function should also inherit the public key + result = await async_level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2", "async": True}) + return result + + @observe() + async def async_level_1_function(*args, **kwargs): + # Only this top-level function receives langfuse_public_key + result = await async_level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1", "async": True}) + return result + + result = await async_level_1_function( + *mock_args, + **mock_kwargs, + langfuse_trace_id=mock_trace_id, + langfuse_public_key=env_public_key, # Only provided to top-level function + ) + + # Use the correct client for flushing + client1.flush() + + assert result == "async_level_3" + + # Verify trace was created properly + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 3 + assert trace_data.name == mock_name + + # Verify all observations have async metadata + async_flags = [ + obs.metadata.get("async") for obs in trace_data.observations if obs.metadata + ] + assert all(async_flags) + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +@pytest.mark.asyncio +async def test_multiproject_mixed_sync_async_context_propagation(): + """Test context propagation between sync and async decorated functions in multi-project setup""" + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_mixed_sync_async_context_propagation" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(as_type="generation") + def sync_level_4_function(): + # Sync function called from async should inherit context + langfuse_client = get_client() + langfuse_client.update_current_generation( + metadata={"level": "4", "type": "sync"} + ) + return "sync_level_4" + + @observe() + async def async_level_3_function(): + # Async function calls sync function + await asyncio.sleep(0.01) + result = sync_level_4_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "3", "type": "async"}) + return result + + @observe() + async def async_level_2_function(): + # Changed to async to avoid event loop issues + result = await async_level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2", "type": "async"}) + return result + + @observe() + async def async_level_1_function(*args, **kwargs): + # Top-level async function + langfuse_client = get_client() + langfuse_client.update_current_trace(name=mock_name) + result = await async_level_2_function() + langfuse_client.update_current_span(metadata={"level": "1", "type": "async"}) + return result + + result = await async_level_1_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key + ) + client1.flush() + + assert result == "sync_level_4" + + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 4 + assert trace_data.name == mock_name + + # Verify mixed sync/async execution + types = [ + obs.metadata.get("type") for obs in trace_data.observations if obs.metadata + ] + assert "sync" in types + assert "async" in types + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +@pytest.mark.asyncio +async def test_multiproject_concurrent_async_context_isolation(): + """Test that concurrent async executions don't interfere with each other's context in multi-project setup""" + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_concurrent_async_context_isolation" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + + trace_id_1 = langfuse.create_trace_id() + trace_id_2 = langfuse.create_trace_id() + + # Use the same valid public key for both tasks to avoid credential issues + # The isolation test is about trace contexts, not different projects + public_key_1 = env_public_key + public_key_2 = env_public_key + + @observe(as_type="generation") + async def async_level_3_function(task_id): + # Simulate work and ensure contexts don't leak + await asyncio.sleep(0.1) # Ensure concurrency overlap + langfuse_client = get_client() + langfuse_client.update_current_generation( + metadata={"task_id": task_id, "level": "3"} + ) + return f"async_level_3_task_{task_id}" + + @observe() + async def async_level_2_function(task_id): + result = await async_level_3_function(task_id) + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"task_id": task_id, "level": "2"}) + return result + + @observe() + async def async_level_1_function(task_id, *args, **kwargs): + langfuse_client = get_client() + langfuse_client.update_current_trace(name=f"{mock_name}_task_{task_id}") + result = await async_level_2_function(task_id) + langfuse_client.update_current_span(metadata={"task_id": task_id, "level": "1"}) + return result + + # Run two concurrent async tasks with the same public key but different trace contexts + task1 = async_level_1_function( + "1", langfuse_trace_id=trace_id_1, langfuse_public_key=public_key_1 + ) + task2 = async_level_1_function( + "2", langfuse_trace_id=trace_id_2, langfuse_public_key=public_key_2 + ) + + result1, result2 = await asyncio.gather(task1, task2) + + client1.flush() + + assert result1 == "async_level_3_task_1" + assert result2 == "async_level_3_task_2" + + # Verify both traces were created correctly and didn't interfere + trace_data_1 = get_api().trace.get(trace_id_1) + trace_data_2 = get_api().trace.get(trace_id_2) + + assert trace_data_1.name == f"{mock_name}_task_1" + assert trace_data_2.name == f"{mock_name}_task_2" + + # Verify that both traces have the expected number of observations (context propagation worked) + assert ( + len(trace_data_1.observations) == 3 + ) # All 3 levels should be captured for task 1 + assert ( + len(trace_data_2.observations) == 3 + ) # All 3 levels should be captured for task 2 + + # Verify traces are properly isolated (no cross-contamination) + trace_1_names = [obs.name for obs in trace_data_1.observations] + trace_2_names = [obs.name for obs in trace_data_2.observations] + assert "async_level_1_function" in trace_1_names + assert "async_level_2_function" in trace_1_names + assert "async_level_3_function" in trace_1_names + assert "async_level_1_function" in trace_2_names + assert "async_level_2_function" in trace_2_names + assert "async_level_3_function" in trace_2_names + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +@pytest.mark.asyncio +async def test_multiproject_async_generator_context_propagation(): + """Test context propagation with async generators in multi-project setup""" + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_async_generator_context_propagation" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(capture_output=True) + async def async_generator_function(): + # Async generator should inherit context from parent + await asyncio.sleep(0.01) + yield "Hello" + await asyncio.sleep(0.01) + yield ", " + await asyncio.sleep(0.01) + yield "Async" + await asyncio.sleep(0.01) + yield " World!" + + @observe() + async def async_consumer_function(): + langfuse_client = get_client() + langfuse_client.update_current_trace(name=mock_name) + + result = "" + async for item in async_generator_function(): + result += item + + langfuse_client.update_current_span( + metadata={"type": "consumer", "result": result} + ) + return result + + result = await async_consumer_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key + ) + client1.flush() + + assert result == "Hello, Async World!" + + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + assert trace_data.name == mock_name + + # Verify both generator and consumer were captured by name (most reliable test) + observation_names = [obs.name for obs in trace_data.observations] + assert "async_generator_function" in observation_names + assert "async_consumer_function" in observation_names + + # Verify that context propagation worked - both functions should be in the same trace + # This confirms that the async generator inherited the public key context + assert len(trace_data.observations) == 2 + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() + + +@pytest.mark.asyncio +async def test_multiproject_async_context_exception_handling(): + """Test that async context is properly restored even when exceptions occur in multi-project setup""" + client1 = Langfuse() # Reads from environment + Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") + + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_async_context_exception_handling" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + @observe(as_type="generation") + async def async_failing_function(): + # This function should inherit context but will raise an exception + await asyncio.sleep(0.01) + langfuse_client = get_client() + langfuse_client.update_current_generation(metadata={"will_fail": True}) + langfuse_client.update_current_trace(name=mock_name) + raise ValueError("Async function failed") + + @observe() + async def async_caller_function(): + try: + await async_failing_function() + except ValueError: + # Context should still be available here + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"caught_exception": True}) + return "exception_handled" + + @observe() + async def async_root_function(*args, **kwargs): + result = await async_caller_function() + # Context should still be available after exception + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"root": True}) + return result + + result = await async_root_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key + ) + client1.flush() + + assert result == "exception_handled" + + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 3 + assert trace_data.name == mock_name + + # Verify exception was properly handled and context maintained + exception_obs = next(obs for obs in trace_data.observations if obs.level == "ERROR") + assert exception_obs.status_message == "Async function failed" + + caught_obs = next( + obs + for obs in trace_data.observations + if obs.metadata and obs.metadata.get("caught_exception") + ) + assert caught_obs is not None + + # Reset instances to not leak to other test suites + removeMockResourceManagerInstances() diff --git a/tests/test_langchain_integration.py b/tests/test_langchain_integration.py index 8b983468f..c45ec98e0 100644 --- a/tests/test_langchain_integration.py +++ b/tests/test_langchain_integration.py @@ -5,6 +5,7 @@ from langchain.schema import StrOutputParser from langchain_openai import ChatOpenAI, OpenAI +from langfuse import Langfuse from langfuse.langchain import CallbackHandler from tests.utils import get_api @@ -18,6 +19,9 @@ def _is_streaming_response(response): # Streaming in chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) def test_stream_chat_models(model_name): name = f"test_stream_chat_models-{create_uuid()}" @@ -28,8 +32,10 @@ def test_stream_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): res = model.stream( [{"role": "user", "content": "return the exact phrase - This is a test!"}], config={"callbacks": [handler]}, @@ -70,6 +76,9 @@ def test_stream_chat_models(model_name): # Streaming in completions models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) def test_stream_completions_models(model_name): name = f"test_stream_completions_models-{create_uuid()}" @@ -78,8 +87,10 @@ def test_stream_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): res = model.stream( "return the exact phrase - This is a test!", config={"callbacks": [handler]}, @@ -119,6 +130,9 @@ def test_stream_completions_models(model_name): # Invoke in chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) def test_invoke_chat_models(model_name): name = f"test_invoke_chat_models-{create_uuid()}" @@ -127,8 +141,10 @@ def test_invoke_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): _ = model.invoke( [{"role": "user", "content": "return the exact phrase - This is a test!"}], config={"callbacks": [handler]}, @@ -164,6 +180,9 @@ def test_invoke_chat_models(model_name): # Invoke in completions models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) def test_invoke_in_completions_models(model_name): name = f"test_invoke_in_completions_models-{create_uuid()}" @@ -172,8 +191,10 @@ def test_invoke_in_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): test_phrase = "This is a test!" _ = model.invoke( f"return the exact phrase - {test_phrase}", @@ -208,6 +229,9 @@ def test_invoke_in_completions_models(model_name): assert generation.latency is not None +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) def test_batch_in_completions_models(model_name): name = f"test_batch_in_completions_models-{create_uuid()}" @@ -216,8 +240,10 @@ def test_batch_in_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): input1 = "Who is the first president of America ?" input2 = "Who is the first president of Ireland ?" _ = model.batch( @@ -252,6 +278,9 @@ def test_batch_in_completions_models(model_name): assert generation.latency is not None +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) def test_batch_in_chat_models(model_name): name = f"test_batch_in_chat_models-{create_uuid()}" @@ -260,8 +289,10 @@ def test_batch_in_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): input1 = "Who is the first president of America ?" input2 = "Who is the first president of Ireland ?" _ = model.batch( @@ -296,6 +327,9 @@ def test_batch_in_chat_models(model_name): # Async stream in chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) async def test_astream_chat_models(model_name): @@ -307,8 +341,10 @@ async def test_astream_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): res = model.astream( [{"role": "user", "content": "Who was the first American president "}], config={"callbacks": [handler]}, @@ -348,6 +384,9 @@ async def test_astream_chat_models(model_name): # Async stream in completions model +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) async def test_astream_completions_models(model_name): @@ -358,8 +397,10 @@ async def test_astream_completions_models(model_name): langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): test_phrase = "This is a test!" res = model.astream( f"return the exact phrase - {test_phrase}", @@ -400,6 +441,9 @@ async def test_astream_completions_models(model_name): # Async invoke in chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) async def test_ainvoke_chat_models(model_name): @@ -409,8 +453,10 @@ async def test_ainvoke_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): test_phrase = "This is a test!" _ = await model.ainvoke( [{"role": "user", "content": f"return the exact phrase - {test_phrase} "}], @@ -446,6 +492,9 @@ async def test_ainvoke_chat_models(model_name): assert generation.latency is not None +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) async def test_ainvoke_in_completions_models(model_name): @@ -455,8 +504,10 @@ async def test_ainvoke_in_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): test_phrase = "This is a test!" _ = await model.ainvoke( f"return the exact phrase - {test_phrase}", @@ -495,6 +546,9 @@ async def test_ainvoke_in_completions_models(model_name): # Sync batch in chains and chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) def test_chains_batch_in_chat_models(model_name): name = f"test_chains_batch_in_chat_models-{create_uuid()}" @@ -503,8 +557,10 @@ def test_chains_batch_in_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt = ChatPromptTemplate.from_template( "tell me a joke about {foo} in 300 words" ) @@ -541,6 +597,9 @@ def test_chains_batch_in_chat_models(model_name): assert generation.latency is not None +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) def test_chains_batch_in_completions_models(model_name): name = f"test_chains_batch_in_completions_models-{create_uuid()}" @@ -549,8 +608,10 @@ def test_chains_batch_in_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt = ChatPromptTemplate.from_template( "tell me a joke about {foo} in 300 words" ) @@ -588,6 +649,9 @@ def test_chains_batch_in_completions_models(model_name): # Async batch call with chains and chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) async def test_chains_abatch_in_chat_models(model_name): @@ -597,8 +661,10 @@ async def test_chains_abatch_in_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt = ChatPromptTemplate.from_template( "tell me a joke about {foo} in 300 words" ) @@ -636,6 +702,9 @@ async def test_chains_abatch_in_chat_models(model_name): # Async batch call with chains and completions models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) async def test_chains_abatch_in_completions_models(model_name): @@ -645,8 +714,10 @@ async def test_chains_abatch_in_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt = ChatPromptTemplate.from_template( "tell me a joke about {foo} in 300 words" ) @@ -680,6 +751,9 @@ async def test_chains_abatch_in_completions_models(model_name): # Async invoke in chains and chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo"]) async def test_chains_ainvoke_chat_models(model_name): @@ -689,8 +763,10 @@ async def test_chains_ainvoke_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt1 = ChatPromptTemplate.from_template( """You are a skilled writer tasked with crafting an engaging introduction for a blog post on the following topic: Topic: {topic} @@ -731,6 +807,9 @@ async def test_chains_ainvoke_chat_models(model_name): # Async invoke in chains and completions models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) async def test_chains_ainvoke_completions_models(model_name): @@ -740,8 +819,10 @@ async def test_chains_ainvoke_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt1 = PromptTemplate.from_template( """You are a skilled writer tasked with crafting an engaging introduction for a blog post on the following topic: Topic: {topic} @@ -780,6 +861,9 @@ async def test_chains_ainvoke_completions_models(model_name): # Async streaming in chat models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo", "gpt-4"]) async def test_chains_astream_chat_models(model_name): @@ -791,8 +875,10 @@ async def test_chains_astream_chat_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt1 = PromptTemplate.from_template( """You are a skilled writer tasked with crafting an engaging introduction for a blog post on the following topic: Topic: {topic} @@ -839,6 +925,9 @@ async def test_chains_astream_chat_models(model_name): # Async Streaming in completions models +@pytest.mark.skip( + reason="This test suite is not properly isolated and fails flakily. TODO: Investigate why" +) @pytest.mark.asyncio @pytest.mark.parametrize("model_name", ["gpt-3.5-turbo-instruct"]) async def test_chains_astream_completions_models(model_name): @@ -848,8 +937,10 @@ async def test_chains_astream_completions_models(model_name): handler = CallbackHandler() langfuse_client = handler.client - with langfuse_client.start_as_current_span(name=name) as span: - trace_id = span.trace_id + trace_id = Langfuse.create_trace_id() + with langfuse_client.start_as_current_span( + name=name, trace_context={"trace_id": trace_id} + ): prompt1 = PromptTemplate.from_template( """You are a skilled writer tasked with crafting an engaging introduction for a blog post on the following topic: Topic: {topic} From 159d5d42f302e8e7013d31df3f332f40fea3e5b0 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:48:03 +0200 Subject: [PATCH 003/296] chore: release v3.2.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 40a439362..48ca7d857 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.1" +__version__ = "3.2.2" diff --git a/pyproject.toml b/pyproject.toml index a7c7958ac..33555b3cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.1" +version = "3.2.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From c9ad8e3fcf064d38f7dec3d9f558ee0f2ab3cf54 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:04:29 +0200 Subject: [PATCH 004/296] perf(langchain): send trace updates on root span (#1284) --- langfuse/langchain/CallbackHandler.py | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index ed7cfb70a..edd21747f 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -149,6 +149,30 @@ def on_retriever_error( except Exception as e: langfuse_logger.exception(e) + def _parse_langfuse_trace_attributes_from_metadata( + self, + metadata: Optional[Dict[str, Any]], + ) -> Dict[str, Any]: + attributes: Dict[str, Any] = {} + + if metadata is None: + return attributes + + if "langfuse_session_id" in metadata and isinstance( + metadata["langfuse_session_id"], str + ): + attributes["session_id"] = metadata["langfuse_session_id"] + + if "langfuse_user_id" in metadata and isinstance( + metadata["langfuse_user_id"], str + ): + attributes["user_id"] = metadata["langfuse_user_id"] + + if "langfuse_tags" in metadata and isinstance(metadata["langfuse_tags"], list): + attributes["tags"] = [str(tag) for tag in metadata["langfuse_tags"]] + + return attributes + def on_chain_start( self, serialized: Optional[Dict[str, Any]], @@ -173,7 +197,7 @@ def on_chain_start( span_level = "DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None if parent_run_id is None: - self.runs[run_id] = self.client.start_span( + span = self.client.start_span( name=span_name, metadata=span_metadata, input=inputs, @@ -182,6 +206,10 @@ def on_chain_start( span_level, ), ) + span.update_trace( + **self._parse_langfuse_trace_attributes_from_metadata(metadata) + ) + self.runs[run_id] = span else: self.runs[run_id] = cast( LangfuseSpan, self.runs[parent_run_id] @@ -1003,7 +1031,12 @@ def _strip_langfuse_keys_from_dict(metadata: Optional[Dict[str, Any]]) -> Any: if metadata is None or not isinstance(metadata, dict): return metadata - langfuse_metadata_keys = ["langfuse_prompt"] + langfuse_metadata_keys = [ + "langfuse_prompt", + "langfuse_session_id", + "langfuse_user_id", + "langfuse_tags", + ] metadata_copy = metadata.copy() From 3d3f334cca49febbfcf7c2683fabd46ab49f12cf Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:05:45 +0200 Subject: [PATCH 005/296] chore: release v3.2.3 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 48ca7d857..018a1b246 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.2" +__version__ = "3.2.3" diff --git a/pyproject.toml b/pyproject.toml index 33555b3cd..e45afb74c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.2" +version = "3.2.3" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 7cbca81c9fa748bbc2e84782960d1a0bc8a5800c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:54:37 +0200 Subject: [PATCH 006/296] chore(deps-dev): bump starlette from 0.40.0 to 0.47.2 (#1274) Bumps [starlette](https://github.com/encode/starlette) from 0.40.0 to 0.47.2. - [Release notes](https://github.com/encode/starlette/releases) - [Changelog](https://github.com/encode/starlette/blob/master/docs/release-notes.md) - [Commits](https://github.com/encode/starlette/compare/0.40.0...0.47.2) --- updated-dependencies: - dependency-name: starlette dependency-version: 0.47.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- poetry.lock | 361 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 304 insertions(+), 57 deletions(-) diff --git a/poetry.lock b/poetry.lock index fbc73e691..ad95e9e46 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,10 +6,12 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "aiohttp" @@ -17,6 +19,7 @@ version = "3.12.14" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906d5075b5ba0dd1c66fcaaf60eb09926a9fef3ca92d912d2a0bbdbecf8b1248"}, {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c875bf6fc2fd1a572aba0e02ef4e7a63694778c5646cdbda346ee24e630d30fb"}, @@ -105,6 +108,7 @@ files = [ {file = "aiohttp-3.12.14-cp39-cp39-win_amd64.whl", hash = "sha256:196858b8820d7f60578f8b47e5669b3195c21d8ab261e39b1d705346458f445f"}, {file = "aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] aiohappyeyeballs = ">=2.5.0" @@ -117,7 +121,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -125,10 +129,12 @@ version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] frozenlist = ">=1.1.0" @@ -140,6 +146,7 @@ version = "0.21.0" description = "asyncio bridge to the standard sqlite3 module" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0"}, {file = "aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3"}, @@ -158,6 +165,7 @@ version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, @@ -169,6 +177,7 @@ version = "0.39.0" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "anthropic-0.39.0-py3-none-any.whl", hash = "sha256:ea17093ae0ce0e1768b0c46501d6086b5bcd74ff39d68cd2d6396374e9de7c09"}, {file = "anthropic-0.39.0.tar.gz", hash = "sha256:94671cc80765f9ce693f76d63a97ee9bef4c2d6063c044e983d21a2e262f63ba"}, @@ -196,6 +205,7 @@ version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, @@ -209,7 +219,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.23)"] [[package]] @@ -218,6 +228,7 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -235,10 +246,12 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] +markers = {main = "extra == \"langchain\" and python_version <= \"3.10\"", dev = "python_version <= \"3.10\""} [[package]] name = "attrs" @@ -246,18 +259,20 @@ version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "backoff" @@ -265,6 +280,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main", "dev"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -276,6 +292,7 @@ version = "2.1.3" description = "A prompt programming language" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "banks-2.1.3-py3-none-any.whl", hash = "sha256:9e1217dc977e6dd1ce42c5ff48e9bcaf238d788c81b42deb6a555615ffcffbab"}, {file = "banks-2.1.3.tar.gz", hash = "sha256:c0dd2cb0c5487274a513a552827e6a8ddbd0ab1a1b967f177e71a6e4748a3ed2"}, @@ -298,6 +315,7 @@ version = "4.1.2" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, @@ -338,6 +356,7 @@ version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" +groups = ["dev"] files = [ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, @@ -359,6 +378,7 @@ version = "1.35.77" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "boto3-1.35.77-py3-none-any.whl", hash = "sha256:a09871805f8e462349a1c33c23eb413668df0bf68424e61d53518e1a7d883b2f"}, {file = "boto3-1.35.77.tar.gz", hash = "sha256:cc819cdbccbc2d0dc185f1dcfe74cf3809489c4cae63c2e5d6a557aa0c5ab928"}, @@ -378,6 +398,7 @@ version = "1.35.77" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "botocore-1.35.77-py3-none-any.whl", hash = "sha256:3faa27d65841499762228902d7e215fa99a4c2fdc76c9113e1c3f339bdf685b8"}, {file = "botocore-1.35.77.tar.gz", hash = "sha256:17b778016644e9342ca3ff2f430c1d1db0c6126e9b41a57cff52ac58e7a455e0"}, @@ -400,6 +421,7 @@ version = "0.0.2" description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -414,6 +436,7 @@ version = "0.5.10" description = "BSON codec for Python" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "bson-0.5.10.tar.gz", hash = "sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590"}, ] @@ -428,6 +451,7 @@ version = "1.2.1" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"}, {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"}, @@ -442,7 +466,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"] typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] uv = ["uv (>=0.1.18)"] virtualenv = ["virtualenv (>=20.0.35)"] @@ -453,6 +477,7 @@ version = "5.3.3" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, @@ -464,6 +489,7 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -475,6 +501,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -486,6 +513,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "dev"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -585,6 +613,7 @@ version = "0.7.6" description = "Chromas fork of hnswlib" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f35192fbbeadc8c0633f0a69c3d3e9f1a4eab3a46b65458bbcbcabdd9e895c36"}, {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f007b608c96362b8f0c8b6b2ac94f67f83fcbabd857c378ae82007ec92f4d82"}, @@ -626,6 +655,7 @@ version = "0.5.5" description = "Chroma." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "chromadb-0.5.5-py3-none-any.whl", hash = "sha256:2a5a4b84cb0fc32b380e193be68cdbadf3d9f77dbbf141649be9886e42910ddd"}, {file = "chromadb-0.5.5.tar.gz", hash = "sha256:84f4bfee320fb4912cbeb4d738f01690891e9894f0ba81f39ee02867102a1c4d"}, @@ -666,6 +696,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -680,6 +711,7 @@ version = "5.8.1" description = "" optional = false python-versions = "<4.0,>=3.8" +groups = ["dev"] files = [ {file = "cohere-5.8.1-py3-none-any.whl", hash = "sha256:92362c651dfbfef8c5d34e95de394578d7197ed7875c6fcbf101e84b60db7fbd"}, {file = "cohere-5.8.1.tar.gz", hash = "sha256:4c0c4468f15f9ad7fb7af15cc9f7305cd6df51243d69e203682be87e9efa5071"}, @@ -704,10 +736,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "extra == \"openai\" and platform_system == \"Windows\""} [[package]] name = "coloredlogs" @@ -715,6 +749,7 @@ version = "15.0.1" description = "Colored terminal output for Python's logging module" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, @@ -732,6 +767,7 @@ version = "1.20.3" description = "dashscope client sdk library" optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "dashscope-1.20.3-py3-none-any.whl", hash = "sha256:8dd4161b8387f1220d38c7a78fccf4a0491d8518949a91ec3e0834c45f46aa78"}, ] @@ -750,6 +786,7 @@ version = "0.6.5" description = "Easily serialize dataclasses to and from JSON." optional = false python-versions = "<4.0,>=3.7" +groups = ["dev"] files = [ {file = "dataclasses_json-0.6.5-py3-none-any.whl", hash = "sha256:f49c77aa3a85cac5bf5b7f65f4790ca0d2be8ef4d92c75e91ba0103072788a39"}, {file = "dataclasses_json-0.6.5.tar.gz", hash = "sha256:1c287594d9fcea72dc42d6d3836cf14848c2dc5ce88f65ed61b36b57f515fe26"}, @@ -765,6 +802,7 @@ version = "0.7.1" description = "XML bomb protection for Python stdlib modules" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, @@ -776,6 +814,7 @@ version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] files = [ {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, @@ -793,6 +832,7 @@ version = "1.0.8" description = "JSON decoder for Python that can extract data from the muck" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53"}, {file = "dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd"}, @@ -804,6 +844,7 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -815,10 +856,12 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "dnspython" @@ -826,6 +869,7 @@ version = "2.6.1" description = "DNS toolkit" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -846,6 +890,7 @@ version = "0.16" description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = false python-versions = ">=3.6,<4.0" +groups = ["dev"] files = [ {file = "docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637"}, {file = "docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e"}, @@ -857,6 +902,8 @@ version = "0.2.0" description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "eval_type_backport-0.2.0-py3-none-any.whl", hash = "sha256:ac2f73d30d40c5a30a80b8739a789d6bb5e49fdffa66d7912667e2015d9c9933"}, {file = "eval_type_backport-0.2.0.tar.gz", hash = "sha256:68796cfbc7371ebf923f03bdf7bef415f3ec098aeced24e054b253a0e78f7b37"}, @@ -871,6 +918,8 @@ version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, @@ -885,6 +934,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -895,23 +945,25 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "fastapi" -version = "0.115.2" +version = "0.116.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "fastapi-0.115.2-py3-none-any.whl", hash = "sha256:61704c71286579cc5a598763905928f24ee98bfcc07aabe84cfefb98812bbc86"}, - {file = "fastapi-0.115.2.tar.gz", hash = "sha256:3995739e0b09fa12f984bce8fa9ae197b35d433750d3d312422d846e283697ee"}, + {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"}, + {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.41.0" +starlette = ">=0.40.0,<0.48.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "fastavro" @@ -919,6 +971,7 @@ version = "1.9.4" description = "Fast read/write of AVRO files" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "fastavro-1.9.4-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:60cb38f07462a7fb4e4440ed0de67d3d400ae6b3d780f81327bebde9aa55faef"}, {file = "fastavro-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:063d01d197fc929c20adc09ca9f0ca86d33ac25ee0963ce0b438244eee8315ae"}, @@ -965,6 +1018,7 @@ version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, @@ -973,7 +1027,7 @@ files = [ [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] +typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[package]] name = "filetype" @@ -981,6 +1035,7 @@ version = "1.2.0" description = "Infer file type and MIME type of any file/buffer. No external dependencies." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, @@ -992,6 +1047,7 @@ version = "24.3.25" description = "The FlatBuffers serialization format for Python" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, @@ -1003,6 +1059,7 @@ version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, @@ -1082,6 +1139,7 @@ files = [ {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "fsspec" @@ -1089,6 +1147,7 @@ version = "2024.3.1" description = "File-system specification" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "fsspec-2024.3.1-py3-none-any.whl", hash = "sha256:918d18d41bf73f0e2b261824baeb1b124bcf771767e3a26425cd7dec3332f512"}, {file = "fsspec-2024.3.1.tar.gz", hash = "sha256:f39780e282d7d117ffb42bb96992f8a90795e4d0fb0f661a70ca39fe9c43ded9"}, @@ -1124,6 +1183,7 @@ version = "2.24.2" description = "Google API client core library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, @@ -1133,15 +1193,15 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] proto-plus = [ - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.22.3,<2.0.0"}, {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" @@ -1149,7 +1209,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -1159,6 +1219,7 @@ version = "2.29.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, @@ -1182,6 +1243,7 @@ version = "1.73.0" description = "Vertex AI API client library" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "google_cloud_aiplatform-1.73.0-py2.py3-none-any.whl", hash = "sha256:6f9aebc1cb2277048093f17214c5f4ec9129fa347b8b22d784f780b12b8865a9"}, {file = "google_cloud_aiplatform-1.73.0.tar.gz", hash = "sha256:687d4d6dd26439db42d38b835ea0da7ebb75c20ca8e17666669536b253637e74"}, @@ -1203,10 +1265,10 @@ shapely = "<3.0.0dev" [package.extras] autologging = ["mlflow (>=1.27.0,<=2.16.0)"] cloud-profiler = ["tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"] -datasets = ["pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)"] +datasets = ["pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0dev) ; python_version < \"3.11\""] endpoint = ["requests (>=2.28.1)"] evaluation = ["pandas (>=1.0.0)", "tqdm (>=4.23.0)"] -full = ["docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.114.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.16.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0)", "ray[default] (>=2.5,<=2.33.0)", "requests (>=2.28.1)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)"] +full = ["docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.114.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.16.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0dev) ; python_version < \"3.11\"", "pyarrow (>=6.0.1)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "requests (>=2.28.1)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0dev)", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)"] langchain = ["langchain (>=0.1.16,<0.4)", "langchain-core (<0.4)", "langchain-google-vertexai (<3)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)"] langchain-testing = ["absl-py", "cloudpickle (>=3.0,<4.0)", "google-cloud-trace (<2)", "langchain (>=0.1.16,<0.4)", "langchain-core (<0.4)", "langchain-google-vertexai (<3)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist"] lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0dev)"] @@ -1214,11 +1276,11 @@ metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"] pipelines = ["pyyaml (>=5.3.1,<7)"] prediction = ["docker (>=5.0.3)", "fastapi (>=0.71.0,<=0.114.0)", "httpx (>=0.23.0,<0.25.0)", "starlette (>=0.17.1)", "uvicorn[standard] (>=0.16.0)"] private-endpoints = ["requests (>=2.28.1)", "urllib3 (>=1.21.1,<1.27)"] -ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0)", "ray[default] (>=2.5,<=2.33.0)", "setuptools (<70.0.0)"] -ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0)", "ray[default] (>=2.5,<=2.33.0)", "ray[train]", "scikit-learn", "setuptools (<70.0.0)", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"] +ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "setuptools (<70.0.0)"] +ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "ray[train]", "scikit-learn", "setuptools (<70.0.0)", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"] reasoningengine = ["cloudpickle (>=3.0,<4.0)", "google-cloud-trace (<2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)"] -tensorboard = ["tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"] -testing = ["aiohttp", "bigframes", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.114.0)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.16.0)", "nltk", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0)", "ray[default] (>=2.5,<=2.33.0)", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "sentencepiece (>=0.2.0)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (==2.13.0)", "tensorflow (==2.16.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0)", "torch (>=2.2.0)", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost"] +tensorboard = ["tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.3.0,<3.0.0dev) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"] +testing = ["aiohttp", "bigframes ; python_version >= \"3.10\"", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.114.0)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.16.0)", "nltk", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0dev) ; python_version < \"3.11\"", "pyarrow (>=6.0.1)", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "sentencepiece (>=0.2.0)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (==2.13.0) ; python_version <= \"3.11\"", "tensorflow (==2.16.1) ; python_version > \"3.11\"", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0) ; python_version <= \"3.11\"", "torch (>=2.2.0) ; python_version > \"3.11\"", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost"] tokenization = ["sentencepiece (>=0.2.0)"] vizier = ["google-vizier (>=0.1.6)"] xai = ["tensorflow (>=2.3.0,<3.0.0dev)"] @@ -1229,6 +1291,7 @@ version = "3.21.0" description = "Google BigQuery API client library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google-cloud-bigquery-3.21.0.tar.gz", hash = "sha256:6265c39f9d5bdf50f11cb81a9c2a0605d285df34ac139de0d2333b1250add0ff"}, {file = "google_cloud_bigquery-3.21.0-py2.py3-none-any.whl", hash = "sha256:83a090aae16b3a687ef22e7b0a1b551e18da615b1c4855c5f312f198959e7739"}, @@ -1244,14 +1307,14 @@ python-dateutil = ">=2.7.2,<3.0dev" requests = ">=2.21.0,<3.0.0dev" [package.extras] -all = ["Shapely (>=1.8.4,<3.0.0dev)", "db-dtypes (>=0.3.0,<2.0.0dev)", "geopandas (>=0.9.0,<1.0dev)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "importlib-metadata (>=1.0.0)", "ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)", "ipywidgets (>=7.7.0)", "opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)", "pandas (>=1.1.0)", "proto-plus (>=1.15.0,<2.0.0dev)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev)", "pyarrow (>=3.0.0)", "tqdm (>=4.7.4,<5.0.0dev)"] +all = ["Shapely (>=1.8.4,<3.0.0dev)", "db-dtypes (>=0.3.0,<2.0.0dev)", "geopandas (>=0.9.0,<1.0dev)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "importlib-metadata (>=1.0.0) ; python_version < \"3.8\"", "ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)", "ipywidgets (>=7.7.0)", "opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)", "pandas (>=1.1.0)", "proto-plus (>=1.15.0,<2.0.0dev)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev)", "pyarrow (>=3.0.0)", "tqdm (>=4.7.4,<5.0.0dev)"] bigquery-v2 = ["proto-plus (>=1.15.0,<2.0.0dev)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev)"] -bqstorage = ["google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "pyarrow (>=3.0.0)"] +bqstorage = ["google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "pyarrow (>=3.0.0)"] geopandas = ["Shapely (>=1.8.4,<3.0.0dev)", "geopandas (>=0.9.0,<1.0dev)"] ipython = ["ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)"] ipywidgets = ["ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)"] opentelemetry = ["opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)"] -pandas = ["db-dtypes (>=0.3.0,<2.0.0dev)", "importlib-metadata (>=1.0.0)", "pandas (>=1.1.0)", "pyarrow (>=3.0.0)"] +pandas = ["db-dtypes (>=0.3.0,<2.0.0dev)", "importlib-metadata (>=1.0.0) ; python_version < \"3.8\"", "pandas (>=1.1.0)", "pyarrow (>=3.0.0)"] tqdm = ["tqdm (>=4.7.4,<5.0.0dev)"] [[package]] @@ -1260,6 +1323,7 @@ version = "2.4.1" description = "Google Cloud API client core library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, @@ -1278,6 +1342,7 @@ version = "1.14.2" description = "Google Cloud Resource Manager API client library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google_cloud_resource_manager-1.14.2-py3-none-any.whl", hash = "sha256:d0fa954dedd1d2b8e13feae9099c01b8aac515b648e612834f9942d2795a9900"}, {file = "google_cloud_resource_manager-1.14.2.tar.gz", hash = "sha256:962e2d904c550d7bac48372607904ff7bb3277e3bb4a36d80cc9a37e28e6eb74"}, @@ -1288,7 +1353,7 @@ google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" grpc-google-iam-v1 = ">=0.14.0,<1.0.0" proto-plus = [ - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.22.3,<2.0.0"}, {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, ] protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" @@ -1299,6 +1364,7 @@ version = "2.18.1" description = "Google Cloud Storage API client library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google_cloud_storage-2.18.1-py2.py3-none-any.whl", hash = "sha256:9d8db6bde3a979cca7150511cd0e4cb363e5f69d31259d890ba1124fa109418c"}, {file = "google_cloud_storage-2.18.1.tar.gz", hash = "sha256:6707a6f30a05aee36faca81296419ca2907ac750af1c0457f278bc9a6fb219ad"}, @@ -1322,6 +1388,7 @@ version = "1.5.0" description = "A python wrapper of the C library 'Google CRC32C'" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, @@ -1402,6 +1469,7 @@ version = "2.7.0" description = "Utilities for Google Media Downloads and Resumable Uploads" optional = false python-versions = ">= 3.7" +groups = ["dev"] files = [ {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, @@ -1420,6 +1488,7 @@ version = "2.4.2" description = "Scrape and search localized results from Google, Bing, Baidu, Yahoo, Yandex, Ebay, Homedepot, youtube at scale using SerpApi.com" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "google_search_results-2.4.2.tar.gz", hash = "sha256:603a30ecae2af8e600b22635757a6df275dad4b934f975e67878ccd640b78245"}, ] @@ -1433,6 +1502,7 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -1451,6 +1521,7 @@ version = "3.0.3" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, @@ -1511,6 +1582,7 @@ files = [ {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, ] +markers = {main = "extra == \"langchain\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and python_version < \"3.10\"", dev = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} [package.extras] docs = ["Sphinx", "furo"] @@ -1522,6 +1594,7 @@ version = "1.7.3" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75"}, {file = "griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b"}, @@ -1536,6 +1609,7 @@ version = "0.5.0" description = "The official Python library for the groq API" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "groq-0.5.0-py3-none-any.whl", hash = "sha256:a7e6be1118bcdfea3ed071ec00f505a34d4e6ec28c435adb5a5afd33545683a1"}, {file = "groq-0.5.0.tar.gz", hash = "sha256:d476cdc3383b45d2a4dc1876142a9542e663ea1029f9e07a05de24f895cae48c"}, @@ -1555,6 +1629,7 @@ version = "0.14.2" description = "IAM API client library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351"}, {file = "grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20"}, @@ -1571,6 +1646,7 @@ version = "1.71.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, @@ -1634,6 +1710,7 @@ version = "1.62.2" description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "grpcio-status-1.62.2.tar.gz", hash = "sha256:62e1bfcb02025a1cd73732a2d33672d3e9d0df4d21c12c51e0bbcaf09bab742a"}, {file = "grpcio_status-1.62.2-py3-none-any.whl", hash = "sha256:206ddf0eb36bc99b033f03b2c8e95d319f0044defae9b41ae21408e7e0cda48f"}, @@ -1650,6 +1727,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1661,6 +1739,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1682,6 +1761,7 @@ version = "0.6.1" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, @@ -1730,6 +1810,7 @@ version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, @@ -1743,7 +1824,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -1754,6 +1835,7 @@ version = "0.4.0" description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, @@ -1765,6 +1847,7 @@ version = "0.24.5" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "huggingface_hub-0.24.5-py3-none-any.whl", hash = "sha256:d93fb63b1f1a919a22ce91a14518974e81fc4610bf344dfe7572343ce8d3aced"}, {file = "huggingface_hub-0.24.5.tar.gz", hash = "sha256:7b45d6744dd53ce9cbf9880957de00e9d10a9ae837f1c9b7255fc8fa4e8264f3"}, @@ -1799,6 +1882,7 @@ version = "10.0" description = "Human friendly output for text interfaces using Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, @@ -1813,6 +1897,7 @@ version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, @@ -1827,6 +1912,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -1838,6 +1924,7 @@ version = "7.0.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "importlib_metadata-7.0.0-py3-none-any.whl", hash = "sha256:d97503976bb81f40a193d41ee6570868479c69d5068651eb039c40d850c59d67"}, {file = "importlib_metadata-7.0.0.tar.gz", hash = "sha256:7fc841f8b8332803464e5dc1c63a2e59121f46ca186c0e2e182e80bf8c1319f7"}, @@ -1849,7 +1936,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "importlib-resources" @@ -1857,6 +1944,7 @@ version = "6.4.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, @@ -1867,7 +1955,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] [[package]] name = "iniconfig" @@ -1875,6 +1963,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -1886,6 +1975,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1903,6 +1993,7 @@ version = "0.4.2" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "jiter-0.4.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c2b003ff58d14f5e182b875acd5177b2367245c19a03be9a2230535d296f7550"}, {file = "jiter-0.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b48c77c25f094707731cd5bad6b776046846b60a27ee20efc8fadfb10a89415f"}, @@ -1966,6 +2057,7 @@ files = [ {file = "jiter-0.4.2-cp39-none-win_amd64.whl", hash = "sha256:bef62cea18521c5b99368147040c7e560c55098a35c93456f110678a2d34189a"}, {file = "jiter-0.4.2.tar.gz", hash = "sha256:29b9d44f23f0c05f46d482f4ebf03213ee290d77999525d0975a17f875bf1eea"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "jmespath" @@ -1973,6 +2065,7 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -1984,6 +2077,7 @@ version = "1.4.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, @@ -1995,10 +2089,12 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -2009,10 +2105,12 @@ version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main", "dev"] files = [ {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "kubernetes" @@ -2020,6 +2118,7 @@ version = "29.0.0" description = "Kubernetes python client" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "kubernetes-29.0.0-py2.py3-none-any.whl", hash = "sha256:ab8cb0e0576ccdfb71886366efb102c6a20f268d817be065ce7f9909c631e43e"}, {file = "kubernetes-29.0.0.tar.gz", hash = "sha256:c4812e227ae74d07d53c88293e564e54b850452715a59a927e7e1bc6b9a60459"}, @@ -2046,10 +2145,12 @@ version = "0.3.12" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" +groups = ["main", "dev"] files = [ {file = "langchain-0.3.12-py3-none-any.whl", hash = "sha256:581ad93a9de12e4b957bc2af9ba8482eb86e3930e84c4ee20ed677da5e2311cd"}, {file = "langchain-0.3.12.tar.gz", hash = "sha256:0d8247afbf37beb263b4adc29f7aa8a5ae83c43a6941894e2f9ba39d5c869e3b"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" @@ -2073,6 +2174,7 @@ version = "0.3.0" description = "An integration package connecting AnthropicMessages and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_anthropic-0.3.0-py3-none-any.whl", hash = "sha256:96b74a9adfcc092cc2ae137d4189ca50e8f5ad9635618024f7c98d8f9fc1076a"}, {file = "langchain_anthropic-0.3.0.tar.gz", hash = "sha256:f9b5cbdbf2d5b3432f78f056e474efb10a2c1e37f9a471d3aceb50a0d9f945df"}, @@ -2090,6 +2192,7 @@ version = "0.2.9" description = "An integration package connecting AWS and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_aws-0.2.9-py3-none-any.whl", hash = "sha256:e6b70017e731d44c3c24c90025b7b199e524c042ae8f8be7094d8bd34ae3adb3"}, {file = "langchain_aws-0.2.9.tar.gz", hash = "sha256:bcc1d4c62addf4ee6cca4e441c63269003e1485069193991e27cae5579bc0353"}, @@ -2110,6 +2213,7 @@ version = "0.3.3" description = "An integration package connecting Cohere and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_cohere-0.3.3-py3-none-any.whl", hash = "sha256:c8dee47a31cedb227ccf3ba93dad5f09ebadf9043e0ce941ae0bffdc3a226b37"}, {file = "langchain_cohere-0.3.3.tar.gz", hash = "sha256:502f35eb5f983656b26114c7411628241fd06f14e24c85721ea57c9ee1c7c890"}, @@ -2132,6 +2236,7 @@ version = "0.3.12" description = "Community contributed LangChain integrations." optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_community-0.3.12-py3-none-any.whl", hash = "sha256:5a993c931d46dc07fcdfcdfa4d87095c5a15d37ff32b0c16e9ecf6f5caa58c9c"}, {file = "langchain_community-0.3.12.tar.gz", hash = "sha256:b4694f34c7214dede03fe5a75e9f335e16bd788dfa6ca279302ad357bf0d0fc4"}, @@ -2160,10 +2265,12 @@ version = "0.3.25" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" +groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.25-py3-none-any.whl", hash = "sha256:e10581c6c74ba16bdc6fdf16b00cced2aa447cc4024ed19746a1232918edde38"}, {file = "langchain_core-0.3.25.tar.gz", hash = "sha256:fdb8df41e5cdd928c0c2551ebbde1cea770ee3c64598395367ad77ddf9acbae7"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" @@ -2183,6 +2290,7 @@ version = "0.3.3" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_experimental-0.3.3-py3-none-any.whl", hash = "sha256:da01aafc162631475f306ca368ecae74d5becd93b8039bddb6315e755e274580"}, {file = "langchain_experimental-0.3.3.tar.gz", hash = "sha256:6bbcdcd084581432ef4b5d732294a59d75a858ede1714b50a5b79bcfe31fa306"}, @@ -2198,6 +2306,7 @@ version = "2.0.9" description = "An integration package connecting Google VertexAI and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_google_vertexai-2.0.9-py3-none-any.whl", hash = "sha256:cb1085cf75793bd03fc825363f2cad9706eb1c30fd263da1998098e4c01e9c8f"}, {file = "langchain_google_vertexai-2.0.9.tar.gz", hash = "sha256:da4a2c08d84a392fa6ab09d8f3f45eb5e86fd5e063ed7b4085c9f62fb04dc1ca"}, @@ -2221,6 +2330,7 @@ version = "0.2.1" description = "An integration package connecting Groq and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_groq-0.2.1-py3-none-any.whl", hash = "sha256:98d282fd9d7d99b0f55de0a1daea2d5d350ef697e3cb5e97de06aeba4eca8679"}, {file = "langchain_groq-0.2.1.tar.gz", hash = "sha256:a59c81d1a15dc97abf4fdb4c2589f98109313eda147e6b378829222d4d929792"}, @@ -2236,6 +2346,7 @@ version = "0.2.3" description = "An integration package connecting Mistral and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_mistralai-0.2.3-py3-none-any.whl", hash = "sha256:960abdd540af527da7e4d68b9316355ece6a740a60d145f32e4e19c1a9fe4d3a"}, {file = "langchain_mistralai-0.2.3.tar.gz", hash = "sha256:2033af92fd82ac7915664885a94579edec19feb3122525fff8dd2787e174c087"}, @@ -2254,6 +2365,7 @@ version = "0.2.1" description = "An integration package connecting Ollama and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_ollama-0.2.1-py3-none-any.whl", hash = "sha256:033916150cc9c341d72274512b9987a0ebf014cf808237687012fc7af4a81ee3"}, {file = "langchain_ollama-0.2.1.tar.gz", hash = "sha256:752b112d233a6e079259cb10138a5af836f42d26781cac6d7eb1b1e0d2ae9a0d"}, @@ -2269,6 +2381,7 @@ version = "0.2.11" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_openai-0.2.11-py3-none-any.whl", hash = "sha256:c019ae915a5782943bee9503388e65c8622d400e0451ef885f3e4989cf35727f"}, {file = "langchain_openai-0.2.11.tar.gz", hash = "sha256:563bd843092d260c7ffd88b8e0e6b830f36347e058e62a6d5e9cc4c461a8da98"}, @@ -2285,10 +2398,12 @@ version = "0.3.3" description = "LangChain text splitting utilities" optional = false python-versions = "<4.0,>=3.9" +groups = ["main", "dev"] files = [ {file = "langchain_text_splitters-0.3.3-py3-none-any.whl", hash = "sha256:c2f8650457685072971edc8c52c9f8826496b3307f28004a7fd09eb32d4d819f"}, {file = "langchain_text_splitters-0.3.3.tar.gz", hash = "sha256:c596958dcab15fdfe0627fd36ce9d588d0a7e35593af70cd10d0a4a06d69b3ee"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] langchain-core = ">=0.3.25,<0.4.0" @@ -2299,6 +2414,7 @@ version = "0.2.62" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = "<4.0,>=3.9.0" +groups = ["dev"] files = [ {file = "langgraph-0.2.62-py3-none-any.whl", hash = "sha256:51ae9e02a52485a837642eebe7ae43269af7d7305d62f8f69ac11589b2fbba26"}, {file = "langgraph-0.2.62.tar.gz", hash = "sha256:0aac9fd55ffe669bc1312203e0f9ea2733c65cc276f196e7ff0d443cf4efbb89"}, @@ -2315,6 +2431,7 @@ version = "2.0.9" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "<4.0.0,>=3.9.0" +groups = ["dev"] files = [ {file = "langgraph_checkpoint-2.0.9-py3-none-any.whl", hash = "sha256:b546ed6129929b8941ac08af6ce5cd26c8ebe1d25883d3c48638d34ade91ce42"}, {file = "langgraph_checkpoint-2.0.9.tar.gz", hash = "sha256:43847d7e385a2d9d2b684155920998e44ed42d2d1780719e4f6111fe3d6db84c"}, @@ -2330,6 +2447,7 @@ version = "0.1.51" description = "SDK for interacting with LangGraph API" optional = false python-versions = "<4.0.0,>=3.9.0" +groups = ["dev"] files = [ {file = "langgraph_sdk-0.1.51-py3-none-any.whl", hash = "sha256:ce2b58466d1700d06149782ed113157a8694a6d7932c801f316cd13fab315fe4"}, {file = "langgraph_sdk-0.1.51.tar.gz", hash = "sha256:dea1363e72562cb1e82a2d156be8d5b1a69ff3fe8815eee0e1e7a2f423242ec1"}, @@ -2345,10 +2463,12 @@ version = "0.1.146" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" +groups = ["main", "dev"] files = [ {file = "langsmith-0.1.146-py3-none-any.whl", hash = "sha256:9d062222f1a32c9b047dab0149b24958f988989cd8d4a5f9139ff959a51e59d8"}, {file = "langsmith-0.1.146.tar.gz", hash = "sha256:ead8b0b9d5b6cd3ac42937ec48bdf09d4afe7ca1bba22dc05eb65591a18106f8"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -2366,6 +2486,7 @@ version = "1.1.9" description = "a modern parsing library" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db"}, {file = "lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba"}, @@ -2383,6 +2504,7 @@ version = "0.12.41" description = "Interface between LLMs and your data" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "llama_index_core-0.12.41-py3-none-any.whl", hash = "sha256:61fc1e912303250ee32778d5db566e242f7bd9ee043940860a436e3c93f17ef5"}, {file = "llama_index_core-0.12.41.tar.gz", hash = "sha256:34f77c51b12b11ee1d743ff183c1b61afe2975f9970357723a744d4e080f5b3d"}, @@ -2421,6 +2543,7 @@ version = "0.5.0" description = "llama-index llms anthropic integration" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "llama_index_llms_anthropic-0.5.0-py3-none-any.whl", hash = "sha256:2b9367db45deabcbda4db1b1216c95e2663e1e6f129570fc2d275207dd3901cf"}, {file = "llama_index_llms_anthropic-0.5.0.tar.gz", hash = "sha256:14e400ccc2deb8e9024ef8cdc24550b67f4240f3563b4564d4870be85de2d9d8"}, @@ -2436,6 +2559,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -2460,6 +2584,7 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -2529,6 +2654,7 @@ version = "3.21.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, @@ -2548,6 +2674,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -2559,6 +2686,7 @@ version = "4.1.0" description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5ac76a8b0cd8095784e51e4c1c9c318c19edcd1709a06eb14979c8d850c31a"}, {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98a49121afdfab67cd80e912b36404139d7deceb6773a83620137aaa0da5714c"}, @@ -2651,6 +2779,7 @@ version = "1.6" description = "An implementation of time.monotonic() for Python 2 & < 3.3" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, @@ -2662,6 +2791,7 @@ version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -2670,7 +2800,7 @@ files = [ [package.extras] develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] +gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] tests = ["pytest (>=4.6)"] [[package]] @@ -2679,6 +2809,7 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, @@ -2752,6 +2883,7 @@ version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, @@ -2844,6 +2976,7 @@ files = [ {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "mypy" @@ -2851,6 +2984,7 @@ version = "1.16.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, @@ -2905,6 +3039,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -2916,6 +3051,7 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -2927,6 +3063,7 @@ version = "3.1" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, @@ -2945,6 +3082,7 @@ version = "3.9.1" description = "Natural Language Toolkit" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, @@ -2970,6 +3108,7 @@ version = "1.8.0" description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +groups = ["dev"] files = [ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, @@ -2984,6 +3123,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -3022,6 +3162,7 @@ files = [ {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "oauthlib" @@ -3029,6 +3170,7 @@ version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, @@ -3045,6 +3187,7 @@ version = "0.4.1" description = "The official Python client for Ollama." optional = false python-versions = "<4.0,>=3.8" +groups = ["dev"] files = [ {file = "ollama-0.4.1-py3-none-any.whl", hash = "sha256:b6fb16aa5a3652633e1716acb12cf2f44aa18beb229329e46a0302734822dfad"}, {file = "ollama-0.4.1.tar.gz", hash = "sha256:8c6b5e7ff80dd0b8692150b03359f60bac7ca162b088c604069409142a684ad3"}, @@ -3060,6 +3203,7 @@ version = "1.17.3" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "onnxruntime-1.17.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d86dde9c0bb435d709e51bd25991c9fe5b9a5b168df45ce119769edc4d198b15"}, {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d87b68bf931ac527b2d3c094ead66bb4381bac4298b65f46c54fe4d1e255865"}, @@ -3102,10 +3246,12 @@ version = "1.93.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "openai-1.93.0-py3-none-any.whl", hash = "sha256:3d746fe5498f0dd72e0d9ab706f26c91c0f646bf7459e5629af8ba7c9dbdf090"}, {file = "openai-1.93.0.tar.gz", hash = "sha256:988f31ade95e1ff0585af11cc5a64510225e4f5cd392698c675d0a9265b8e337"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -3129,6 +3275,7 @@ version = "1.33.1" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "opentelemetry_api-1.33.1-py3-none-any.whl", hash = "sha256:4db83ebcf7ea93e64637ec6ee6fabee45c5cbe4abd9cf3da95c43828ddb50b83"}, {file = "opentelemetry_api-1.33.1.tar.gz", hash = "sha256:1c6055fc0a2d3f23a50c7e17e16ef75ad489345fd3df1f8b8af7c0bbf8a109e8"}, @@ -3144,6 +3291,7 @@ version = "1.33.1" description = "OpenTelemetry Collector Exporters" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp-1.33.1-py3-none-any.whl", hash = "sha256:9bcf1def35b880b55a49e31ebd63910edac14b294fd2ab884953c4deaff5b300"}, {file = "opentelemetry_exporter_otlp-1.33.1.tar.gz", hash = "sha256:4d050311ea9486e3994575aa237e32932aad58330a31fba24fdba5c0d531cf04"}, @@ -3159,6 +3307,7 @@ version = "1.33.1" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.33.1-py3-none-any.whl", hash = "sha256:b81c1de1ad349785e601d02715b2d29d6818aed2c809c20219f3d1f20b038c36"}, {file = "opentelemetry_exporter_otlp_proto_common-1.33.1.tar.gz", hash = "sha256:c57b3fa2d0595a21c4ed586f74f948d259d9949b58258f11edb398f246bec131"}, @@ -3173,6 +3322,7 @@ version = "1.33.1" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.33.1-py3-none-any.whl", hash = "sha256:7e8da32c7552b756e75b4f9e9c768a61eb47dee60b6550b37af541858d669ce1"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.33.1.tar.gz", hash = "sha256:345696af8dc19785fac268c8063f3dc3d5e274c774b308c634f39d9c21955728"}, @@ -3196,6 +3346,7 @@ version = "1.33.1" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.33.1-py3-none-any.whl", hash = "sha256:ebd6c523b89a2ecba0549adb92537cc2bf647b4ee61afbbd5a4c6535aa3da7cf"}, {file = "opentelemetry_exporter_otlp_proto_http-1.33.1.tar.gz", hash = "sha256:46622d964a441acb46f463ebdc26929d9dec9efb2e54ef06acdc7305e8593c38"}, @@ -3216,6 +3367,7 @@ version = "0.54b1" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation-0.54b1-py3-none-any.whl", hash = "sha256:a4ae45f4a90c78d7006c51524f57cd5aa1231aef031eae905ee34d5423f5b198"}, {file = "opentelemetry_instrumentation-0.54b1.tar.gz", hash = "sha256:7658bf2ff914b02f246ec14779b66671508125c0e4227361e56b5ebf6cef0aec"}, @@ -3233,6 +3385,7 @@ version = "0.54b1" description = "ASGI instrumentation for OpenTelemetry" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation_asgi-0.54b1-py3-none-any.whl", hash = "sha256:84674e822b89af563b283a5283c2ebb9ed585d1b80a1c27fb3ac20b562e9f9fc"}, {file = "opentelemetry_instrumentation_asgi-0.54b1.tar.gz", hash = "sha256:ab4df9776b5f6d56a78413c2e8bbe44c90694c67c844a1297865dc1bd926ed3c"}, @@ -3254,6 +3407,7 @@ version = "0.54b1" description = "OpenTelemetry FastAPI Instrumentation" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation_fastapi-0.54b1-py3-none-any.whl", hash = "sha256:fb247781cfa75fd09d3d8713c65e4a02bd1e869b00e2c322cc516d4b5429860c"}, {file = "opentelemetry_instrumentation_fastapi-0.54b1.tar.gz", hash = "sha256:1fcad19cef0db7092339b571a59e6f3045c9b58b7fd4670183f7addc459d78df"}, @@ -3275,6 +3429,7 @@ version = "1.33.1" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "opentelemetry_proto-1.33.1-py3-none-any.whl", hash = "sha256:243d285d9f29663fc7ea91a7171fcc1ccbbfff43b48df0774fd64a37d98eda70"}, {file = "opentelemetry_proto-1.33.1.tar.gz", hash = "sha256:9627b0a5c90753bf3920c398908307063e4458b287bb890e5c1d6fa11ad50b68"}, @@ -3289,6 +3444,7 @@ version = "1.33.1" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "opentelemetry_sdk-1.33.1-py3-none-any.whl", hash = "sha256:19ea73d9a01be29cacaa5d6c8ce0adc0b7f7b4d58cc52f923e4413609f670112"}, {file = "opentelemetry_sdk-1.33.1.tar.gz", hash = "sha256:85b9fcf7c3d23506fbc9692fd210b8b025a1920535feec50bd54ce203d57a531"}, @@ -3305,6 +3461,7 @@ version = "0.54b1" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "opentelemetry_semantic_conventions-0.54b1-py3-none-any.whl", hash = "sha256:29dab644a7e435b58d3a3918b58c333c92686236b30f7891d5e51f02933ca60d"}, {file = "opentelemetry_semantic_conventions-0.54b1.tar.gz", hash = "sha256:d1cecedae15d19bdaafca1e56b29a66aa286f50b5d08f036a145c7f3e9ef9cee"}, @@ -3320,6 +3477,7 @@ version = "0.54b1" description = "Web util for OpenTelemetry" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "opentelemetry_util_http-0.54b1-py3-none-any.whl", hash = "sha256:b1c91883f980344a1c3c486cffd47ae5c9c1dd7323f9cbe9fdb7cadb401c87c9"}, {file = "opentelemetry_util_http-0.54b1.tar.gz", hash = "sha256:f0b66868c19fbaf9c9d4e11f4a7599fa15d5ea50b884967a26ccd9d72c7c9d15"}, @@ -3331,6 +3489,7 @@ version = "3.10.2" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "orjson-3.10.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:87124c1b3471a072fda422e156dd7ef086d854937d68adc266f17f32a1043c95"}, {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1b79526bd039e775ad0f558800c3cd9f3bde878a1268845f63984d37bcbb5d1"}, @@ -3379,6 +3538,7 @@ files = [ {file = "orjson-3.10.2-cp39-none-win_amd64.whl", hash = "sha256:161f3b4e6364132562af80967ac3211e6681d320a01954da4915af579caab0b2"}, {file = "orjson-3.10.2.tar.gz", hash = "sha256:47affe9f704c23e49a0fbb9d441af41f602474721e8639e8814640198f9ae32f"}, ] +markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} [[package]] name = "overrides" @@ -3386,6 +3546,7 @@ version = "7.7.0" description = "A decorator to automatically detect mismatch when overriding a method." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, @@ -3397,6 +3558,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -3408,6 +3570,7 @@ version = "2.0.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, @@ -3440,7 +3603,7 @@ files = [ numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.21.0", markers = "python_version == \"3.10\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -3475,6 +3638,7 @@ version = "0.9.0" description = "Parameterized testing with any Python test framework" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, @@ -3489,6 +3653,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -3500,6 +3665,7 @@ version = "14.7.0" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"}, {file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"}, @@ -3519,6 +3685,7 @@ version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, @@ -3607,7 +3774,7 @@ docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -3616,6 +3783,7 @@ version = "4.2.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, @@ -3632,6 +3800,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -3647,6 +3816,7 @@ version = "3.5.0" description = "Integrate PostHog into any python application." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "posthog-3.5.0-py2.py3-none-any.whl", hash = "sha256:3c672be7ba6f95d555ea207d4486c171d06657eb34b3ce25eb043bfe7b6b5b76"}, {file = "posthog-3.5.0.tar.gz", hash = "sha256:8f7e3b2c6e8714d0c0c542a2109b83a7549f63b7113a133ab2763a89245ef2ef"}, @@ -3670,6 +3840,7 @@ version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, @@ -3688,6 +3859,7 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -3788,6 +3960,7 @@ files = [ {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "proto-plus" @@ -3795,6 +3968,7 @@ version = "1.26.1" description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -3812,6 +3986,7 @@ version = "5.29.5" description = "" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079"}, {file = "protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc"}, @@ -3832,6 +4007,7 @@ version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, @@ -3843,6 +4019,7 @@ version = "0.4.0" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, @@ -3857,6 +4034,7 @@ version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, @@ -3872,7 +4050,7 @@ typing-extensions = [ [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] [[package]] name = "pydantic-core" @@ -3880,6 +4058,7 @@ version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, @@ -3981,6 +4160,7 @@ version = "2.6.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"}, {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"}, @@ -4001,13 +4181,14 @@ version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] -plugins = ["importlib-metadata"] +plugins = ["importlib-metadata ; python_version < \"3.8\""] windows-terminal = ["colorama (>=0.4.6)"] [[package]] @@ -4016,6 +4197,7 @@ version = "4.7.3" description = "Python driver for MongoDB " optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pymongo-4.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e9580b4537b3cc5d412070caabd1dabdf73fdce249793598792bac5782ecf2eb"}, {file = "pymongo-4.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:517243b2b189c98004570dd8fc0e89b1a48363d5578b3b99212fa2098b2ea4b8"}, @@ -4084,9 +4266,9 @@ dnspython = ">=1.16.0,<3.0.0" [package.extras] aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] -encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] +ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] test = ["pytest (>=7)"] zstd = ["zstandard"] @@ -4097,6 +4279,7 @@ version = "0.48.9" description = "A SQL query builder API for Python" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378"}, ] @@ -4107,6 +4290,7 @@ version = "1.1.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"}, {file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"}, @@ -4118,6 +4302,8 @@ version = "3.4.1" description = "A python implementation of GNU readline." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, @@ -4129,6 +4315,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -4151,6 +4338,7 @@ version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -4169,6 +4357,7 @@ version = "1.0.12" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_httpserver-1.0.12-py3-none-any.whl", hash = "sha256:dae1c79ec7aeda83bfaaf4d0a400867a4b1bc6bf668244daaf13aa814e3022da"}, {file = "pytest_httpserver-1.0.12.tar.gz", hash = "sha256:c14600b8efb9ea8d7e63251a242ab987f13028b36d3d397ffaca3c929f67eb16"}, @@ -4183,6 +4372,7 @@ version = "2.3.1" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, @@ -4197,6 +4387,7 @@ version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, @@ -4217,6 +4408,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -4231,6 +4423,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -4245,6 +4438,7 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -4256,6 +4450,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -4309,6 +4504,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "regex" @@ -4316,6 +4512,7 @@ version = "2024.4.28" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, @@ -4404,6 +4601,7 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -4425,6 +4623,7 @@ version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=3.4" +groups = ["dev"] files = [ {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, @@ -4443,10 +4642,12 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" @@ -4457,6 +4658,7 @@ version = "0.21.1" description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "respx-0.21.1-py2.py3-none-any.whl", hash = "sha256:05f45de23f0c785862a2c92a3e173916e8ca88e4caad715dd5f68584d6053c20"}, {file = "respx-0.21.1.tar.gz", hash = "sha256:0bd7fe21bfaa52106caa1223ce61224cf30786985f17c63c5d71eff0307ee8af"}, @@ -4471,6 +4673,7 @@ version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, @@ -4489,6 +4692,7 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = false python-versions = ">=3.6,<4" +groups = ["dev"] files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -4503,6 +4707,7 @@ version = "0.5.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, @@ -4530,6 +4735,7 @@ version = "0.10.1" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">= 3.8" +groups = ["dev"] files = [ {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, @@ -4547,19 +4753,20 @@ version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shapely" @@ -4567,6 +4774,7 @@ version = "2.0.4" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f"}, {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17"}, @@ -4624,6 +4832,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -4635,6 +4844,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -4646,6 +4856,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -4657,6 +4868,7 @@ version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, @@ -4668,6 +4880,7 @@ version = "2.0.29" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, @@ -4719,6 +4932,7 @@ files = [ {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""} @@ -4751,21 +4965,22 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "starlette" -version = "0.40.0" +version = "0.47.2" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "starlette-0.40.0-py3-none-any.whl", hash = "sha256:c494a22fae73805376ea6bf88439783ecfba9aac88a43911b48c653437e784c4"}, - {file = "starlette-0.40.0.tar.gz", hash = "sha256:1a3139688fb298ce5e2d661d37046a66ad996ce94be4d4983be019a23a04ea35"}, + {file = "starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b"}, + {file = "starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8"}, ] [package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] name = "sympy" @@ -4773,6 +4988,7 @@ version = "1.12" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, @@ -4787,6 +5003,7 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -4801,10 +5018,12 @@ version = "8.2.3" description = "Retry code until it succeeds" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx", "tornado (>=4.5)"] @@ -4815,6 +5034,7 @@ version = "0.7.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, @@ -4867,6 +5087,7 @@ version = "0.19.1" description = "" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "tokenizers-0.19.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:952078130b3d101e05ecfc7fc3640282d74ed26bcf691400f872563fca15ac97"}, {file = "tokenizers-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82c8b8063de6c0468f08e82c4e198763e7b97aabfe573fd4cf7b33930ca4df77"}, @@ -4984,6 +5205,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -4995,10 +5218,12 @@ version = "4.66.3" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53"}, {file = "tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -5015,6 +5240,7 @@ version = "0.12.3" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, @@ -5032,6 +5258,7 @@ version = "2.31.0.6" description = "Typing stubs for requests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, @@ -5046,6 +5273,7 @@ version = "1.26.25.14" description = "Typing stubs for urllib3" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, @@ -5057,6 +5285,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -5068,6 +5297,7 @@ version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -5083,6 +5313,7 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["dev"] files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -5094,14 +5325,16 @@ version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "dev"] +markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -5110,13 +5343,15 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -5127,6 +5362,7 @@ version = "0.29.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -5140,12 +5376,12 @@ httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standar python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -5153,6 +5389,8 @@ version = "0.19.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" files = [ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, @@ -5189,7 +5427,7 @@ files = [ [package.extras] docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0) ; python_version >= \"3.12\"", "aiohttp (>=3.8.1) ; python_version < \"3.12\"", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] [[package]] name = "virtualenv" @@ -5197,6 +5435,7 @@ version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, @@ -5209,7 +5448,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchfiles" @@ -5217,6 +5456,7 @@ version = "0.21.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, @@ -5304,6 +5544,7 @@ version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -5320,6 +5561,7 @@ version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, @@ -5401,6 +5643,7 @@ version = "3.0.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, @@ -5418,6 +5661,7 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -5497,6 +5741,7 @@ version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -5581,6 +5826,7 @@ files = [ {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] idna = ">=2.0" @@ -5593,6 +5839,7 @@ version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, @@ -5608,6 +5855,6 @@ llama-index = [] openai = ["openai"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<4.0" content-hash = "ac31e0db93cfcbf0adbb201a20b1547d1af5347437062d55142d3b24838f711e" From a288086f36d6a1ccb1380f49a0470fc5dedd2cfb Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Sat, 9 Aug 2025 11:11:15 +0200 Subject: [PATCH 007/296] chore: remove unnecessary deps (#1285) --- langfuse/_utils/serializer.py | 2 +- poetry.lock | 6762 ++++++++------------------------- pyproject.toml | 24 - tests/test_extract_model.py | 158 - tests/test_json.py | 9 - tests/test_langchain.py | 138 - tests/test_serializer.py | 35 - tests/utils.py | 29 - 8 files changed, 1680 insertions(+), 5477 deletions(-) delete mode 100644 tests/test_extract_model.py diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 9232cc908..289531b23 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -28,7 +28,7 @@ class Serializable: # type: ignore # Attempt to import numpy try: - import numpy as np + import numpy as np # type: ignore[import-not-found] except ImportError: np = None # type: ignore diff --git a/poetry.lock b/poetry.lock index ad95e9e46..644f9c5de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3257 +1,1058 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -description = "Happy Eyeballs for asyncio" +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] +python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -markers = {main = "extra == \"langchain\""} [[package]] -name = "aiohttp" -version = "3.12.14" -description = "Async http client/server framework (asyncio)" +name = "anyio" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906d5075b5ba0dd1c66fcaaf60eb09926a9fef3ca92d912d2a0bbdbecf8b1248"}, - {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c875bf6fc2fd1a572aba0e02ef4e7a63694778c5646cdbda346ee24e630d30fb"}, - {file = "aiohttp-3.12.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbb284d15c6a45fab030740049d03c0ecd60edad9cd23b211d7e11d3be8d56fd"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e360381e02e1a05d36b223ecab7bc4a6e7b5ab15760022dc92589ee1d4238c"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aaf90137b5e5d84a53632ad95ebee5c9e3e7468f0aab92ba3f608adcb914fa95"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e532a25e4a0a2685fa295a31acf65e027fbe2bea7a4b02cdfbbba8a064577663"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eab9762c4d1b08ae04a6c77474e6136da722e34fdc0e6d6eab5ee93ac29f35d1"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe53c3812b2899889a7fca763cdfaeee725f5be68ea89905e4275476ffd7e61"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5760909b7080aa2ec1d320baee90d03b21745573780a072b66ce633eb77a8656"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:02fcd3f69051467bbaa7f84d7ec3267478c7df18d68b2e28279116e29d18d4f3"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dcd1172cd6794884c33e504d3da3c35648b8be9bfa946942d353b939d5f1288"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:224d0da41355b942b43ad08101b1b41ce633a654128ee07e36d75133443adcda"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e387668724f4d734e865c1776d841ed75b300ee61059aca0b05bce67061dcacc"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:dec9cde5b5a24171e0b0a4ca064b1414950904053fb77c707efd876a2da525d8"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bbad68a2af4877cc103cd94af9160e45676fc6f0c14abb88e6e092b945c2c8e3"}, - {file = "aiohttp-3.12.14-cp310-cp310-win32.whl", hash = "sha256:ee580cb7c00bd857b3039ebca03c4448e84700dc1322f860cf7a500a6f62630c"}, - {file = "aiohttp-3.12.14-cp310-cp310-win_amd64.whl", hash = "sha256:cf4f05b8cea571e2ccc3ca744e35ead24992d90a72ca2cf7ab7a2efbac6716db"}, - {file = "aiohttp-3.12.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4552ff7b18bcec18b60a90c6982049cdb9dac1dba48cf00b97934a06ce2e597"}, - {file = "aiohttp-3.12.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8283f42181ff6ccbcf25acaae4e8ab2ff7e92b3ca4a4ced73b2c12d8cd971393"}, - {file = "aiohttp-3.12.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:040afa180ea514495aaff7ad34ec3d27826eaa5d19812730fe9e529b04bb2179"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b413c12f14c1149f0ffd890f4141a7471ba4b41234fe4fd4a0ff82b1dc299dbb"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d6f607ce2e1a93315414e3d448b831238f1874b9968e1195b06efaa5c87e245"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:565e70d03e924333004ed101599902bba09ebb14843c8ea39d657f037115201b"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4699979560728b168d5ab63c668a093c9570af2c7a78ea24ca5212c6cdc2b641"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5fdf6af93ec6c99bf800eba3af9a43d8bfd66dce920ac905c817ef4a712afe"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ac76627c0b7ee0e80e871bde0d376a057916cb008a8f3ffc889570a838f5cc7"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:798204af1180885651b77bf03adc903743a86a39c7392c472891649610844635"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4f1205f97de92c37dd71cf2d5bcfb65fdaed3c255d246172cce729a8d849b4da"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:76ae6f1dd041f85065d9df77c6bc9c9703da9b5c018479d20262acc3df97d419"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a194ace7bc43ce765338ca2dfb5661489317db216ea7ea700b0332878b392cab"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:16260e8e03744a6fe3fcb05259eeab8e08342c4c33decf96a9dad9f1187275d0"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c779e5ebbf0e2e15334ea404fcce54009dc069210164a244d2eac8352a44b28"}, - {file = "aiohttp-3.12.14-cp311-cp311-win32.whl", hash = "sha256:a289f50bf1bd5be227376c067927f78079a7bdeccf8daa6a9e65c38bae14324b"}, - {file = "aiohttp-3.12.14-cp311-cp311-win_amd64.whl", hash = "sha256:0b8a69acaf06b17e9c54151a6c956339cf46db4ff72b3ac28516d0f7068f4ced"}, - {file = "aiohttp-3.12.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a0ecbb32fc3e69bc25efcda7d28d38e987d007096cbbeed04f14a6662d0eee22"}, - {file = "aiohttp-3.12.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0400f0ca9bb3e0b02f6466421f253797f6384e9845820c8b05e976398ac1d81a"}, - {file = "aiohttp-3.12.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a56809fed4c8a830b5cae18454b7464e1529dbf66f71c4772e3cfa9cbec0a1ff"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f2e373276e4755691a963e5d11756d093e346119f0627c2d6518208483fb6d"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ca39e433630e9a16281125ef57ece6817afd1d54c9f1bf32e901f38f16035869"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c748b3f8b14c77720132b2510a7d9907a03c20ba80f469e58d5dfd90c079a1c"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a568abe1b15ce69d4cc37e23020720423f0728e3cb1f9bcd3f53420ec3bfe7"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9888e60c2c54eaf56704b17feb558c7ed6b7439bca1e07d4818ab878f2083660"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3006a1dc579b9156de01e7916d38c63dc1ea0679b14627a37edf6151bc530088"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa8ec5c15ab80e5501a26719eb48a55f3c567da45c6ea5bb78c52c036b2655c7"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:39b94e50959aa07844c7fe2206b9f75d63cc3ad1c648aaa755aa257f6f2498a9"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04c11907492f416dad9885d503fbfc5dcb6768d90cad8639a771922d584609d3"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:88167bd9ab69bb46cee91bd9761db6dfd45b6e76a0438c7e884c3f8160ff21eb"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:791504763f25e8f9f251e4688195e8b455f8820274320204f7eafc467e609425"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785b112346e435dd3a1a67f67713a3fe692d288542f1347ad255683f066d8e0"}, - {file = "aiohttp-3.12.14-cp312-cp312-win32.whl", hash = "sha256:15f5f4792c9c999a31d8decf444e79fcfd98497bf98e94284bf390a7bb8c1729"}, - {file = "aiohttp-3.12.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b66e1a182879f579b105a80d5c4bd448b91a57e8933564bf41665064796a338"}, - {file = "aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767"}, - {file = "aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e"}, - {file = "aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758"}, - {file = "aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5"}, - {file = "aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa"}, - {file = "aiohttp-3.12.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b8cc6b05e94d837bcd71c6531e2344e1ff0fb87abe4ad78a9261d67ef5d83eae"}, - {file = "aiohttp-3.12.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1dcb015ac6a3b8facd3677597edd5ff39d11d937456702f0bb2b762e390a21b"}, - {file = "aiohttp-3.12.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3779ed96105cd70ee5e85ca4f457adbce3d9ff33ec3d0ebcdf6c5727f26b21b3"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:717a0680729b4ebd7569c1dcd718c46b09b360745fd8eb12317abc74b14d14d0"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b5dd3a2ef7c7e968dbbac8f5574ebeac4d2b813b247e8cec28174a2ba3627170"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4710f77598c0092239bc12c1fcc278a444e16c7032d91babf5abbf7166463f7b"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3e9f75ae842a6c22a195d4a127263dbf87cbab729829e0bd7857fb1672400b2"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f9c8d55d6802086edd188e3a7d85a77787e50d56ce3eb4757a3205fa4657922"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79b29053ff3ad307880d94562cca80693c62062a098a5776ea8ef5ef4b28d140"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23e1332fff36bebd3183db0c7a547a1da9d3b4091509f6d818e098855f2f27d3"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a564188ce831fd110ea76bcc97085dd6c625b427db3f1dbb14ca4baa1447dcbc"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a7a1b4302f70bb3ec40ca86de82def532c97a80db49cac6a6700af0de41af5ee"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1b07ccef62950a2519f9bfc1e5b294de5dd84329f444ca0b329605ea787a3de5"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:938bd3ca6259e7e48b38d84f753d548bd863e0c222ed6ee6ace3fd6752768a84"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8bc784302b6b9f163b54c4e93d7a6f09563bd01ff2b841b29ed3ac126e5040bf"}, - {file = "aiohttp-3.12.14-cp39-cp39-win32.whl", hash = "sha256:a3416f95961dd7d5393ecff99e3f41dc990fb72eda86c11f2a60308ac6dcd7a0"}, - {file = "aiohttp-3.12.14-cp39-cp39-win_amd64.whl", hash = "sha256:196858b8820d7f60578f8b47e5669b3195c21d8ab261e39b1d705346458f445f"}, - {file = "aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2"}, + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] -aiohappyeyeballs = ">=2.5.0" -aiosignal = ">=1.4.0" -async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -propcache = ">=0.2.0" -yarl = ">=1.17.0,<2.0" +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] +trio = ["trio (>=0.26.1)"] [[package]] -name = "aiosignal" -version = "1.4.0" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = true +python-versions = ">=3.7" files = [ - {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, - {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -frozenlist = ">=1.1.0" -typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] -name = "aiosqlite" -version = "0.21.0" -description = "asyncio bridge to the standard sqlite3 module" +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.7,<4.0" files = [ - {file = "aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0"}, - {file = "aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3"}, + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] -[package.dependencies] -typing_extensions = ">=4.0" - -[package.extras] -dev = ["attribution (==1.7.1)", "black (==24.3.0)", "build (>=1.2)", "coverage[toml] (==7.6.10)", "flake8 (==7.0.0)", "flake8-bugbear (==24.12.12)", "flit (==3.10.1)", "mypy (==1.14.1)", "ufmt (==2.5.1)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==8.1.3)", "sphinx-mdinclude (==0.6.1)"] +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" [[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] -name = "anthropic" -version = "0.39.0" -description = "The official Python library for the anthropic API" +name = "charset-normalizer" +version = "3.4.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.7" files = [ - {file = "anthropic-0.39.0-py3-none-any.whl", hash = "sha256:ea17093ae0ce0e1768b0c46501d6086b5bcd74ff39d68cd2d6396374e9de7c09"}, - {file = "anthropic-0.39.0.tar.gz", hash = "sha256:94671cc80765f9ce693f76d63a97ee9bef4c2d6063c044e983d21a2e262f63ba"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] -[package.dependencies] -anyio = ">=3.5.0,<5" -boto3 = {version = ">=1.28.57", optional = true, markers = "extra == \"bedrock\""} -botocore = {version = ">=1.31.57", optional = true, markers = "extra == \"bedrock\""} -distro = ">=1.7.0,<2" -google-auth = {version = ">=2,<3", optional = true, markers = "extra == \"vertex\""} -httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -typing-extensions = ">=4.7,<5" - -[package.extras] -bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] -vertex = ["google-auth (>=2,<3)"] - [[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] -trio = ["trio (>=0.23)"] - [[package]] -name = "asgiref" -version = "3.8.1" -description = "ASGI specs, helper code, and adapters" +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = "*" files = [ - {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, - {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[package.dependencies] -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} - -[package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] - [[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] +python-versions = ">=3.6" files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -markers = {main = "extra == \"langchain\" and python_version <= \"3.10\"", dev = "python_version <= \"3.10\""} [[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] -markers = {main = "extra == \"langchain\""} + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +test = ["pytest (>=6)"] [[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" +name = "execnet" +version = "2.1.1" +description = "execnet: rapid multi-Python deployment" optional = false -python-versions = ">=3.7,<4.0" -groups = ["main", "dev"] +python-versions = ">=3.8" files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + [[package]] -name = "banks" -version = "2.1.3" -description = "A prompt programming language" +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "banks-2.1.3-py3-none-any.whl", hash = "sha256:9e1217dc977e6dd1ce42c5ff48e9bcaf238d788c81b42deb6a555615ffcffbab"}, - {file = "banks-2.1.3.tar.gz", hash = "sha256:c0dd2cb0c5487274a513a552827e6a8ddbd0ab1a1b967f177e71a6e4748a3ed2"}, + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] -[package.dependencies] -deprecated = "*" -eval-type-backport = {version = "*", markers = "python_version < \"3.10\""} -griffe = "*" -jinja2 = "*" -platformdirs = "*" -pydantic = "*" - [package.extras] -all = ["litellm", "redis"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] -name = "bcrypt" -version = "4.1.2" -description = "Modern password hashing for your software and your servers" +name = "googleapis-common-protos" +version = "1.70.0" +description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, - {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, - {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, - {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, - {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, - {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, - {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, + {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, + {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, ] +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + [package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] +grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -groups = ["dev"] +name = "greenlet" +version = "3.2.4" +description = "Lightweight in-process concurrent programming" +optional = true +python-versions = ">=3.9" files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, + {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, + {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, + {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, + {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, + {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, + {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, + {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, ] -[package.dependencies] -soupsieve = ">1.2" - [package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil", "setuptools"] + +[[package]] +name = "grpcio" +version = "1.74.0" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.9" +files = [ + {file = "grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907"}, + {file = "grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb"}, + {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486"}, + {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11"}, + {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9"}, + {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc"}, + {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e"}, + {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82"}, + {file = "grpcio-1.74.0-cp310-cp310-win32.whl", hash = "sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7"}, + {file = "grpcio-1.74.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5"}, + {file = "grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31"}, + {file = "grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4"}, + {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce"}, + {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3"}, + {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182"}, + {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d"}, + {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f"}, + {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4"}, + {file = "grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b"}, + {file = "grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11"}, + {file = "grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8"}, + {file = "grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6"}, + {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5"}, + {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49"}, + {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7"}, + {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3"}, + {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707"}, + {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b"}, + {file = "grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c"}, + {file = "grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc"}, + {file = "grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89"}, + {file = "grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01"}, + {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e"}, + {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91"}, + {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249"}, + {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362"}, + {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f"}, + {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20"}, + {file = "grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa"}, + {file = "grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24"}, + {file = "grpcio-1.74.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4bc5fca10aaf74779081e16c2bcc3d5ec643ffd528d9e7b1c9039000ead73bae"}, + {file = "grpcio-1.74.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:6bab67d15ad617aff094c382c882e0177637da73cbc5532d52c07b4ee887a87b"}, + {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:655726919b75ab3c34cdad39da5c530ac6fa32696fb23119e36b64adcfca174a"}, + {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a2b06afe2e50ebfd46247ac3ba60cac523f54ec7792ae9ba6073c12daf26f0a"}, + {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f251c355167b2360537cf17bea2cf0197995e551ab9da6a0a59b3da5e8704f9"}, + {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f7b5882fb50632ab1e48cb3122d6df55b9afabc265582808036b6e51b9fd6b7"}, + {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:834988b6c34515545b3edd13e902c1acdd9f2465d386ea5143fb558f153a7176"}, + {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22b834cef33429ca6cc28303c9c327ba9a3fafecbf62fae17e9a7b7163cc43ac"}, + {file = "grpcio-1.74.0-cp39-cp39-win32.whl", hash = "sha256:7d95d71ff35291bab3f1c52f52f474c632db26ea12700c2ff0ea0532cb0b5854"}, + {file = "grpcio-1.74.0-cp39-cp39-win_amd64.whl", hash = "sha256:ecde9ab49f58433abe02f9ed076c7b5be839cf0153883a6d23995937a82392fa"}, + {file = "grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.74.0)"] [[package]] -name = "boto3" -version = "1.35.77" -description = "The AWS SDK for Python" +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "boto3-1.35.77-py3-none-any.whl", hash = "sha256:a09871805f8e462349a1c33c23eb413668df0bf68424e61d53518e1a7d883b2f"}, - {file = "boto3-1.35.77.tar.gz", hash = "sha256:cc819cdbccbc2d0dc185f1dcfe74cf3809489c4cae63c2e5d6a557aa0c5ab928"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] -[package.dependencies] -botocore = ">=1.35.77,<1.36.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.10.0,<0.11.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - [[package]] -name = "botocore" -version = "1.35.77" -description = "Low-level, data-driven core of boto 3." +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "botocore-1.35.77-py3-none-any.whl", hash = "sha256:3faa27d65841499762228902d7e215fa99a4c2fdc76c9113e1c3f339bdf685b8"}, - {file = "botocore-1.35.77.tar.gz", hash = "sha256:17b778016644e9342ca3ff2f430c1d1db0c6126e9b41a57cff52ac58e7a455e0"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, -] +certifi = "*" +h11 = ">=0.16" [package.extras] -crt = ["awscrt (==0.22.0)"] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] -name = "bs4" -version = "0.0.2" -description = "Dummy package for Beautiful Soup (beautifulsoup4)" +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." optional = false -python-versions = "*" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, - {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] -beautifulsoup4 = "*" +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] -name = "bson" -version = "0.5.10" -description = "BSON codec for Python" +name = "identify" +version = "2.6.12" +description = "File identification library for Python" optional = false -python-versions = "*" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "bson-0.5.10.tar.gz", hash = "sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590"}, + {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, + {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, ] -[package.dependencies] -python-dateutil = ">=2.4.0" -six = ">=1.9.0" +[package.extras] +license = ["ukkonen"] [[package]] -name = "build" -version = "1.2.1" -description = "A simple, correct Python build frontend" +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.6" files = [ - {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"}, - {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] -[package.dependencies] -colorama = {version = "*", markers = "os_name == \"nt\""} -importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} -packaging = ">=19.1" -pyproject_hooks = "*" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - [package.extras] -docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"] -typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] -uv = ["uv (>=0.1.18)"] -virtualenv = ["virtualenv (>=20.0.35)"] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] -name = "cachetools" -version = "5.3.3" -description = "Extensible memoizing collections and decorators" +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" optional = false -python-versions = ">=3.7" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, ] -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, -] +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." optional = false -python-versions = ">=3.7.0" -groups = ["main", "dev"] +python-versions = ">=3.7" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] -name = "chroma-hnswlib" -version = "0.7.6" -description = "Chromas fork of hnswlib" +name = "jiter" +version = "0.10.0" +description = "Fast iterable JSON parser." optional = false -python-versions = "*" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f35192fbbeadc8c0633f0a69c3d3e9f1a4eab3a46b65458bbcbcabdd9e895c36"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f007b608c96362b8f0c8b6b2ac94f67f83fcbabd857c378ae82007ec92f4d82"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:456fd88fa0d14e6b385358515aef69fc89b3c2191706fd9aee62087b62aad09c"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dfaae825499c2beaa3b75a12d7ec713b64226df72a5c4097203e3ed532680da"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:2487201982241fb1581be26524145092c95902cb09fc2646ccfbc407de3328ec"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2fe6ea949047beed19a94b33f41fe882a691e58b70c55fdaa90274ae78be046f"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feceff971e2a2728c9ddd862a9dd6eb9f638377ad98438876c9aeac96c9482f5"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0633b60e00a2b92314d0bf5bbc0da3d3320be72c7e3f4a9b19f4609dc2b2ab"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a566abe32fab42291f766d667bdbfa234a7f457dcbd2ba19948b7a978c8ca624"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6be47853d9a58dedcfa90fc846af202b071f028bbafe1d8711bf64fe5a7f6111"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a7af35bdd39a88bffa49f9bb4bf4f9040b684514a024435a1ef5cdff980579d"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a53b1f1551f2b5ad94eb610207bde1bb476245fc5097a2bec2b476c653c58bde"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3085402958dbdc9ff5626ae58d696948e715aef88c86d1e3f9285a88f1afd3bc"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:77326f658a15adfb806a16543f7db7c45f06fd787d699e643642d6bde8ed49c4"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93b056ab4e25adab861dfef21e1d2a2756b18be5bc9c292aa252fa12bb44e6ae"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fe91f018b30452c16c811fd6c8ede01f84e5a9f3c23e0758775e57f1c3778871"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c0e627476f0f4d9e153420d36042dd9c6c3671cfd1fe511c0253e38c2a1039"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e9796a4536b7de6c6d76a792ba03e08f5aaa53e97e052709568e50b4d20c04f"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:d30e2db08e7ffdcc415bd072883a322de5995eb6ec28a8f8c054103bbd3ec1e0"}, - {file = "chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7"}, + {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, + {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, + {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, + {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, + {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, + {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, + {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, + {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, + {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, + {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, + {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, + {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, + {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, + {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, + {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, + {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, + {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, + {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, + {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, + {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, + {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, + {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, + {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, + {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, + {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, + {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, + {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, + {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, + {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, + {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, + {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, + {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, + {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, + {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, + {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, + {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, + {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, + {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, + {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, + {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, + {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] -[package.dependencies] -numpy = "*" - [[package]] -name = "chromadb" -version = "0.5.5" -description = "Chroma." +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ - {file = "chromadb-0.5.5-py3-none-any.whl", hash = "sha256:2a5a4b84cb0fc32b380e193be68cdbadf3d9f77dbbf141649be9886e42910ddd"}, - {file = "chromadb-0.5.5.tar.gz", hash = "sha256:84f4bfee320fb4912cbeb4d738f01690891e9894f0ba81f39ee02867102a1c4d"}, + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] [package.dependencies] -bcrypt = ">=4.0.1" -build = ">=1.0.3" -chroma-hnswlib = "0.7.6" -fastapi = ">=0.95.2" -grpcio = ">=1.58.0" -httpx = ">=0.27.0" -importlib-resources = "*" -kubernetes = ">=28.1.0" -mmh3 = ">=4.0.1" -numpy = ">=1.22.5,<2.0.0" -onnxruntime = ">=1.14.1" -opentelemetry-api = ">=1.2.0" -opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0" -opentelemetry-instrumentation-fastapi = ">=0.41b0" -opentelemetry-sdk = ">=1.2.0" -orjson = ">=3.9.12" -overrides = ">=7.3.1" -posthog = ">=2.4.0" -pydantic = ">=1.9" -pypika = ">=0.48.9" -PyYAML = ">=6.0.0" -tenacity = ">=8.2.3" -tokenizers = ">=0.13.2" -tqdm = ">=4.65.0" -typer = ">=0.9.0" -typing-extensions = ">=4.5.0" -uvicorn = {version = ">=0.18.3", extras = ["standard"]} +jsonpointer = ">=1.9" [[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] -name = "cohere" -version = "5.8.1" -description = "" -optional = false -python-versions = "<4.0,>=3.8" -groups = ["dev"] +name = "langchain" +version = "0.3.27" +description = "Building applications with LLMs through composability" +optional = true +python-versions = "<4.0,>=3.9" files = [ - {file = "cohere-5.8.1-py3-none-any.whl", hash = "sha256:92362c651dfbfef8c5d34e95de394578d7197ed7875c6fcbf101e84b60db7fbd"}, - {file = "cohere-5.8.1.tar.gz", hash = "sha256:4c0c4468f15f9ad7fb7af15cc9f7305cd6df51243d69e203682be87e9efa5071"}, + {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, + {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, ] [package.dependencies] -boto3 = ">=1.34.0,<2.0.0" -fastavro = ">=1.9.4,<2.0.0" -httpx = ">=0.21.2" -httpx-sse = "0.4.0" -parameterized = ">=0.9.0,<0.10.0" -pydantic = ">=1.9.2" -pydantic-core = ">=2.18.2,<3.0.0" -requests = ">=2.0.0,<3.0.0" -tokenizers = ">=0.15,<1" -types-requests = ">=2.0.0,<3.0.0" -typing_extensions = ">=4.0.0" - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "extra == \"openai\" and platform_system == \"Windows\""} - -[[package]] -name = "coloredlogs" -version = "15.0.1" -description = "Colored terminal output for Python's logging module" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["dev"] -files = [ - {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, - {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, -] - -[package.dependencies] -humanfriendly = ">=9.1" - -[package.extras] -cron = ["capturer (>=2.4)"] - -[[package]] -name = "dashscope" -version = "1.20.3" -description = "dashscope client sdk library" -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "dashscope-1.20.3-py3-none-any.whl", hash = "sha256:8dd4161b8387f1220d38c7a78fccf4a0491d8518949a91ec3e0834c45f46aa78"}, -] - -[package.dependencies] -aiohttp = "*" -requests = "*" -websocket-client = "*" - -[package.extras] -tokenizer = ["tiktoken"] - -[[package]] -name = "dataclasses-json" -version = "0.6.5" -description = "Easily serialize dataclasses to and from JSON." -optional = false -python-versions = "<4.0,>=3.7" -groups = ["dev"] -files = [ - {file = "dataclasses_json-0.6.5-py3-none-any.whl", hash = "sha256:f49c77aa3a85cac5bf5b7f65f4790ca0d2be8ef4d92c75e91ba0103072788a39"}, - {file = "dataclasses_json-0.6.5.tar.gz", hash = "sha256:1c287594d9fcea72dc42d6d3836cf14848c2dc5ce88f65ed61b36b57f515fe26"}, -] - -[package.dependencies] -marshmallow = ">=3.18.0,<4.0.0" -typing-inspect = ">=0.4.0,<1" - -[[package]] -name = "defusedxml" -version = "0.7.1" -description = "XML bomb protection for Python stdlib modules" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["dev"] -files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] - -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - -[[package]] -name = "dirtyjson" -version = "1.0.8" -description = "JSON decoder for Python that can extract data from the muck" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53"}, - {file = "dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd"}, -] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] -markers = {main = "extra == \"openai\""} - -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "docstring-parser" -version = "0.16" -description = "Parse Python docstrings in reST, Google and Numpydoc format" -optional = false -python-versions = ">=3.6,<4.0" -groups = ["dev"] -files = [ - {file = "docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637"}, - {file = "docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e"}, -] - -[[package]] -name = "eval-type-backport" -version = "0.2.0" -description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.10\"" -files = [ - {file = "eval_type_backport-0.2.0-py3-none-any.whl", hash = "sha256:ac2f73d30d40c5a30a80b8739a789d6bb5e49fdffa66d7912667e2015d9c9933"}, - {file = "eval_type_backport-0.2.0.tar.gz", hash = "sha256:68796cfbc7371ebf923f03bdf7bef415f3ec098aeced24e054b253a0e78f7b37"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "exceptiongroup" -version = "1.2.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version <= \"3.10\"" -files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.1" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "fastapi" -version = "0.116.1" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"}, - {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.48.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] -standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "fastavro" -version = "1.9.4" -description = "Fast read/write of AVRO files" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "fastavro-1.9.4-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:60cb38f07462a7fb4e4440ed0de67d3d400ae6b3d780f81327bebde9aa55faef"}, - {file = "fastavro-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:063d01d197fc929c20adc09ca9f0ca86d33ac25ee0963ce0b438244eee8315ae"}, - {file = "fastavro-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a9053fcfbc895f2a16a4303af22077e3a8fdcf1cd5d6ed47ff2ef22cbba2f0"}, - {file = "fastavro-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:02bf1276b7326397314adf41b34a4890f6ffa59cf7e0eb20b9e4ab0a143a1598"}, - {file = "fastavro-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56bed9eca435389a8861e6e2d631ec7f8f5dda5b23f93517ac710665bd34ca29"}, - {file = "fastavro-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:0cd2099c8c672b853e0b20c13e9b62a69d3fbf67ee7c59c7271ba5df1680310d"}, - {file = "fastavro-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af8c6d8c43a02b5569c093fc5467469541ac408c79c36a5b0900d3dd0b3ba838"}, - {file = "fastavro-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a138710bd61580324d23bc5e3df01f0b82aee0a76404d5dddae73d9e4c723f"}, - {file = "fastavro-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:903d97418120ca6b6a7f38a731166c1ccc2c4344ee5e0470d09eb1dc3687540a"}, - {file = "fastavro-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c443eeb99899d062dbf78c525e4614dd77e041a7688fa2710c224f4033f193ae"}, - {file = "fastavro-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ac26ab0774d1b2b7af6d8f4300ad20bbc4b5469e658a02931ad13ce23635152f"}, - {file = "fastavro-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:cf7247874c22be856ba7d1f46a0f6e0379a6025f1a48a7da640444cbac6f570b"}, - {file = "fastavro-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:68912f2020e1b3d70557260b27dd85fb49a4fc6bfab18d384926127452c1da4c"}, - {file = "fastavro-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6925ce137cdd78e109abdb0bc33aad55de6c9f2d2d3036b65453128f2f5f5b92"}, - {file = "fastavro-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b928cd294e36e35516d0deb9e104b45be922ba06940794260a4e5dbed6c192a"}, - {file = "fastavro-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:90c9838bc4c991ffff5dd9d88a0cc0030f938b3fdf038cdf6babde144b920246"}, - {file = "fastavro-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:eca6e54da571b06a3c5a72dbb7212073f56c92a6fbfbf847b91c347510f8a426"}, - {file = "fastavro-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4b02839ac261100cefca2e2ad04cdfedc556cb66b5ec735e0db428e74b399de"}, - {file = "fastavro-1.9.4-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:4451ee9a305a73313a1558d471299f3130e4ecc10a88bf5742aa03fb37e042e6"}, - {file = "fastavro-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8524fccfb379565568c045d29b2ebf71e1f2c0dd484aeda9fe784ef5febe1a8"}, - {file = "fastavro-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33d0a00a6e09baa20f6f038d7a2ddcb7eef0e7a9980e947a018300cb047091b8"}, - {file = "fastavro-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23d7e5b29c9bf6f26e8be754b2c8b919838e506f78ef724de7d22881696712fc"}, - {file = "fastavro-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e6ab3ee53944326460edf1125b2ad5be2fadd80f7211b13c45fa0c503b4cf8d"}, - {file = "fastavro-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:64d335ec2004204c501f8697c385d0a8f6b521ac82d5b30696f789ff5bc85f3c"}, - {file = "fastavro-1.9.4-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7e05f44c493e89e73833bd3ff3790538726906d2856f59adc8103539f4a1b232"}, - {file = "fastavro-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:253c63993250bff4ee7b11fb46cf3a4622180a783bedc82a24c6fdcd1b10ca2a"}, - {file = "fastavro-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24d6942eb1db14640c2581e0ecd1bbe0afc8a83731fcd3064ae7f429d7880cb7"}, - {file = "fastavro-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d47bb66be6091cd48cfe026adcad11c8b11d7d815a2949a1e4ccf03df981ca65"}, - {file = "fastavro-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c293897f12f910e58a1024f9c77f565aa8e23b36aafda6ad8e7041accc57a57f"}, - {file = "fastavro-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:f05d2afcb10a92e2a9e580a3891f090589b3e567fdc5641f8a46a0b084f120c3"}, - {file = "fastavro-1.9.4.tar.gz", hash = "sha256:56b8363e360a1256c94562393dc7f8611f3baf2b3159f64fb2b9c6b87b14e876"}, -] - -[package.extras] -codecs = ["cramjam", "lz4", "zstandard"] -lz4 = ["lz4"] -snappy = ["cramjam"] -zstandard = ["zstandard"] - -[[package]] -name = "filelock" -version = "3.14.0" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] - -[[package]] -name = "filetype" -version = "1.2.0" -description = "Infer file type and MIME type of any file/buffer. No external dependencies." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, - {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, -] - -[[package]] -name = "flatbuffers" -version = "24.3.25" -description = "The FlatBuffers serialization format for Python" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, - {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, -] - -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] -markers = {main = "extra == \"langchain\""} - -[[package]] -name = "fsspec" -version = "2024.3.1" -description = "File-system specification" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "fsspec-2024.3.1-py3-none-any.whl", hash = "sha256:918d18d41bf73f0e2b261824baeb1b124bcf771767e3a26425cd7dec3332f512"}, - {file = "fsspec-2024.3.1.tar.gz", hash = "sha256:f39780e282d7d117ffb42bb96992f8a90795e4d0fb0f661a70ca39fe9c43ded9"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -devel = ["pytest", "pytest-cov"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -tqdm = ["tqdm"] - -[[package]] -name = "google-api-core" -version = "2.24.2" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, - {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.0" -googleapis-common-protos = ">=1.56.2,<2.0.0" -grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""}, - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, -] -grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, -] -proto-plus = [ - {version = ">=1.22.3,<2.0.0"}, - {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" -requests = ">=2.18.0,<3.0.0" - -[package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-auth" -version = "2.29.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, - {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-cloud-aiplatform" -version = "1.73.0" -description = "Vertex AI API client library" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "google_cloud_aiplatform-1.73.0-py2.py3-none-any.whl", hash = "sha256:6f9aebc1cb2277048093f17214c5f4ec9129fa347b8b22d784f780b12b8865a9"}, - {file = "google_cloud_aiplatform-1.73.0.tar.gz", hash = "sha256:687d4d6dd26439db42d38b835ea0da7ebb75c20ca8e17666669536b253637e74"}, -] - -[package.dependencies] -docstring-parser = "<1" -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.8.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<3.0.0dev" -google-cloud-bigquery = ">=1.15.0,<3.20.0 || >3.20.0,<4.0.0dev" -google-cloud-resource-manager = ">=1.3.3,<3.0.0dev" -google-cloud-storage = ">=1.32.0,<3.0.0dev" -packaging = ">=14.3" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" -pydantic = "<3" -shapely = "<3.0.0dev" - -[package.extras] -autologging = ["mlflow (>=1.27.0,<=2.16.0)"] -cloud-profiler = ["tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"] -datasets = ["pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0dev) ; python_version < \"3.11\""] -endpoint = ["requests (>=2.28.1)"] -evaluation = ["pandas (>=1.0.0)", "tqdm (>=4.23.0)"] -full = ["docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.114.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.16.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0dev) ; python_version < \"3.11\"", "pyarrow (>=6.0.1)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "requests (>=2.28.1)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0dev)", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)"] -langchain = ["langchain (>=0.1.16,<0.4)", "langchain-core (<0.4)", "langchain-google-vertexai (<3)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)"] -langchain-testing = ["absl-py", "cloudpickle (>=3.0,<4.0)", "google-cloud-trace (<2)", "langchain (>=0.1.16,<0.4)", "langchain-core (<0.4)", "langchain-google-vertexai (<3)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist"] -lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0dev)"] -metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"] -pipelines = ["pyyaml (>=5.3.1,<7)"] -prediction = ["docker (>=5.0.3)", "fastapi (>=0.71.0,<=0.114.0)", "httpx (>=0.23.0,<0.25.0)", "starlette (>=0.17.1)", "uvicorn[standard] (>=0.16.0)"] -private-endpoints = ["requests (>=2.28.1)", "urllib3 (>=1.21.1,<1.27)"] -ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "setuptools (<70.0.0)"] -ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0)", "pyarrow (>=6.0.1)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "ray[train]", "scikit-learn", "setuptools (<70.0.0)", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"] -reasoningengine = ["cloudpickle (>=3.0,<4.0)", "google-cloud-trace (<2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)"] -tensorboard = ["tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (>=2.3.0,<3.0.0dev) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"] -testing = ["aiohttp", "bigframes ; python_version >= \"3.10\"", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.114.0)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.16.0)", "nltk", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pyarrow (>=10.0.1) ; python_version == \"3.11\"", "pyarrow (>=14.0.0) ; python_version >= \"3.12\"", "pyarrow (>=3.0.0,<8.0dev) ; python_version < \"3.11\"", "pyarrow (>=6.0.1)", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<2.10.dev0 || >=2.33.dev0,<=2.33.0) ; python_version < \"3.11\"", "ray[default] (>=2.5,<=2.33.0) ; python_version == \"3.11\"", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "sentencepiece (>=0.2.0)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<2.18.0)", "tensorflow (==2.13.0) ; python_version <= \"3.11\"", "tensorflow (==2.16.1) ; python_version > \"3.11\"", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev) ; python_version <= \"3.11\"", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0) ; python_version <= \"3.11\"", "torch (>=2.2.0) ; python_version > \"3.11\"", "tqdm (>=4.23.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost"] -tokenization = ["sentencepiece (>=0.2.0)"] -vizier = ["google-vizier (>=0.1.6)"] -xai = ["tensorflow (>=2.3.0,<3.0.0dev)"] - -[[package]] -name = "google-cloud-bigquery" -version = "3.21.0" -description = "Google BigQuery API client library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google-cloud-bigquery-3.21.0.tar.gz", hash = "sha256:6265c39f9d5bdf50f11cb81a9c2a0605d285df34ac139de0d2333b1250add0ff"}, - {file = "google_cloud_bigquery-3.21.0-py2.py3-none-any.whl", hash = "sha256:83a090aae16b3a687ef22e7b0a1b551e18da615b1c4855c5f312f198959e7739"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<3.0.0dev" -google-cloud-core = ">=1.6.0,<3.0.0dev" -google-resumable-media = ">=0.6.0,<3.0dev" -packaging = ">=20.0.0" -python-dateutil = ">=2.7.2,<3.0dev" -requests = ">=2.21.0,<3.0.0dev" - -[package.extras] -all = ["Shapely (>=1.8.4,<3.0.0dev)", "db-dtypes (>=0.3.0,<2.0.0dev)", "geopandas (>=0.9.0,<1.0dev)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "importlib-metadata (>=1.0.0) ; python_version < \"3.8\"", "ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)", "ipywidgets (>=7.7.0)", "opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)", "pandas (>=1.1.0)", "proto-plus (>=1.15.0,<2.0.0dev)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev)", "pyarrow (>=3.0.0)", "tqdm (>=4.7.4,<5.0.0dev)"] -bigquery-v2 = ["proto-plus (>=1.15.0,<2.0.0dev)", "protobuf (>=3.19.5,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev)"] -bqstorage = ["google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "pyarrow (>=3.0.0)"] -geopandas = ["Shapely (>=1.8.4,<3.0.0dev)", "geopandas (>=0.9.0,<1.0dev)"] -ipython = ["ipykernel (>=6.0.0)", "ipython (>=7.23.1,!=8.1.0)"] -ipywidgets = ["ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)"] -opentelemetry = ["opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)"] -pandas = ["db-dtypes (>=0.3.0,<2.0.0dev)", "importlib-metadata (>=1.0.0) ; python_version < \"3.8\"", "pandas (>=1.1.0)", "pyarrow (>=3.0.0)"] -tqdm = ["tqdm (>=4.7.4,<5.0.0dev)"] - -[[package]] -name = "google-cloud-core" -version = "2.4.1" -description = "Google Cloud API client core library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, - {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-resource-manager" -version = "1.14.2" -description = "Google Cloud Resource Manager API client library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google_cloud_resource_manager-1.14.2-py3-none-any.whl", hash = "sha256:d0fa954dedd1d2b8e13feae9099c01b8aac515b648e612834f9942d2795a9900"}, - {file = "google_cloud_resource_manager-1.14.2.tar.gz", hash = "sha256:962e2d904c550d7bac48372607904ff7bb3277e3bb4a36d80cc9a37e28e6eb74"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" -grpc-google-iam-v1 = ">=0.14.0,<1.0.0" -proto-plus = [ - {version = ">=1.22.3,<2.0.0"}, - {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, -] -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[[package]] -name = "google-cloud-storage" -version = "2.18.1" -description = "Google Cloud Storage API client library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google_cloud_storage-2.18.1-py2.py3-none-any.whl", hash = "sha256:9d8db6bde3a979cca7150511cd0e4cb363e5f69d31259d890ba1124fa109418c"}, - {file = "google_cloud_storage-2.18.1.tar.gz", hash = "sha256:6707a6f30a05aee36faca81296419ca2907ac750af1c0457f278bc9a6fb219ad"}, -] - -[package.dependencies] -google-api-core = ">=2.15.0,<3.0.0dev" -google-auth = ">=2.26.1,<3.0dev" -google-cloud-core = ">=2.3.0,<3.0dev" -google-crc32c = ">=1.0,<2.0dev" -google-resumable-media = ">=2.6.0" -requests = ">=2.18.0,<3.0.0dev" - -[package.extras] -protobuf = ["protobuf (<6.0.0dev)"] -tracing = ["opentelemetry-api (>=1.1.0)"] - -[[package]] -name = "google-crc32c" -version = "1.5.0" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, - {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, - {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, - {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, - {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, - {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, - {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, - {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, - {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, - {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, - {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, - {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, - {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, - {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, - {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, - {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-resumable-media" -version = "2.7.0" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = false -python-versions = ">= 3.7" -groups = ["dev"] -files = [ - {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, - {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0dev" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] - -[[package]] -name = "google-search-results" -version = "2.4.2" -description = "Scrape and search localized results from Google, Bing, Baidu, Yahoo, Yandex, Ebay, Homedepot, youtube at scale using SerpApi.com" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "google_search_results-2.4.2.tar.gz", hash = "sha256:603a30ecae2af8e600b22635757a6df275dad4b934f975e67878ccd640b78245"}, -] - -[package.dependencies] -requests = "*" - -[[package]] -name = "googleapis-common-protos" -version = "1.70.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, - {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, -] - -[package.dependencies] -grpcio = {version = ">=1.44.0,<2.0.0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0)"] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] -markers = {main = "extra == \"langchain\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and python_version < \"3.10\"", dev = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "griffe" -version = "1.7.3" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75"}, - {file = "griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b"}, -] - -[package.dependencies] -colorama = ">=0.4" - -[[package]] -name = "groq" -version = "0.5.0" -description = "The official Python library for the groq API" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "groq-0.5.0-py3-none-any.whl", hash = "sha256:a7e6be1118bcdfea3ed071ec00f505a34d4e6ec28c435adb5a5afd33545683a1"}, - {file = "groq-0.5.0.tar.gz", hash = "sha256:d476cdc3383b45d2a4dc1876142a9542e663ea1029f9e07a05de24f895cae48c"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -typing-extensions = ">=4.7,<5" - -[[package]] -name = "grpc-google-iam-v1" -version = "0.14.2" -description = "IAM API client library" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351"}, - {file = "grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20"}, -] - -[package.dependencies] -googleapis-common-protos = {version = ">=1.56.0,<2.0.0", extras = ["grpc"]} -grpcio = ">=1.44.0,<2.0.0" -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[[package]] -name = "grpcio" -version = "1.71.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, - {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, - {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"}, - {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"}, - {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"}, - {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"}, - {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"}, - {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"}, - {file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"}, - {file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"}, - {file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"}, - {file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"}, - {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"}, - {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"}, - {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"}, - {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"}, - {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"}, - {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"}, - {file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"}, - {file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"}, - {file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"}, - {file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"}, - {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"}, - {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"}, - {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"}, - {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"}, - {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"}, - {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"}, - {file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"}, - {file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"}, - {file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"}, - {file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"}, - {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"}, - {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"}, - {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"}, - {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"}, - {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"}, - {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"}, - {file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"}, - {file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"}, - {file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"}, - {file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"}, - {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"}, - {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"}, - {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"}, - {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"}, - {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"}, - {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"}, - {file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"}, - {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"}, - {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.71.0)"] - -[[package]] -name = "grpcio-status" -version = "1.62.2" -description = "Status proto mapping for gRPC" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "grpcio-status-1.62.2.tar.gz", hash = "sha256:62e1bfcb02025a1cd73732a2d33672d3e9d0df4d21c12c51e0bbcaf09bab742a"}, - {file = "grpcio_status-1.62.2-py3-none-any.whl", hash = "sha256:206ddf0eb36bc99b033f03b2c8e95d319f0044defae9b41ae21408e7e0cda48f"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.2" -protobuf = ">=4.21.6" - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.16" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "httpx" -version = "0.27.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "httpx-sse" -version = "0.4.0" -description = "Consume Server-Sent Event (SSE) messages with HTTPX." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, - {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, -] - -[[package]] -name = "huggingface-hub" -version = "0.24.5" -description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "huggingface_hub-0.24.5-py3-none-any.whl", hash = "sha256:d93fb63b1f1a919a22ce91a14518974e81fc4610bf344dfe7572343ce8d3aced"}, - {file = "huggingface_hub-0.24.5.tar.gz", hash = "sha256:7b45d6744dd53ce9cbf9880957de00e9d10a9ae837f1c9b7255fc8fa4e8264f3"}, -] - -[package.dependencies] -filelock = "*" -fsspec = ">=2023.5.0" -packaging = ">=20.9" -pyyaml = ">=5.1" -requests = "*" -tqdm = ">=4.42.1" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -hf-transfer = ["hf-transfer (>=0.1.4)"] -inference = ["aiohttp", "minijinja (>=1.0)"] -quality = ["mypy (==1.5.1)", "ruff (>=0.5.0)"] -tensorflow = ["graphviz", "pydot", "tensorflow"] -tensorflow-testing = ["keras (<3.0)", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] -torch = ["safetensors[torch]", "torch"] -typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] - -[[package]] -name = "humanfriendly" -version = "10.0" -description = "Human friendly output for text interfaces using Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["dev"] -files = [ - {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, - {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, -] - -[package.dependencies] -pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} - -[[package]] -name = "identify" -version = "2.5.36" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -groups = ["main", "dev"] -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "importlib-metadata" -version = "7.0.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "importlib_metadata-7.0.0-py3-none-any.whl", hash = "sha256:d97503976bb81f40a193d41ee6570868479c69d5068651eb039c40d850c59d67"}, - {file = "importlib_metadata-7.0.0.tar.gz", hash = "sha256:7fc841f8b8332803464e5dc1c63a2e59121f46ca186c0e2e182e80bf8c1319f7"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "importlib-resources" -version = "6.4.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, - {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["dev", "docs"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jiter" -version = "0.4.2" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "jiter-0.4.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c2b003ff58d14f5e182b875acd5177b2367245c19a03be9a2230535d296f7550"}, - {file = "jiter-0.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b48c77c25f094707731cd5bad6b776046846b60a27ee20efc8fadfb10a89415f"}, - {file = "jiter-0.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f50ad6b172bde4d45f4d4ea10c49282a337b8bb735afc99763dfa55ea84a743"}, - {file = "jiter-0.4.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95f6001e86f525fbbc9706db2078dc22be078b0950de55b92d37041930f5f940"}, - {file = "jiter-0.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16646ef23b62b007de80460d303ebb2d81e355dac9389c787cec87cdd7ffef2f"}, - {file = "jiter-0.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b4e847c13b0bf1255c711a92330e7a8cb8b5cdd1e37d7db309627bcdd3367ff"}, - {file = "jiter-0.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c536589be60e4c5f2b20fadc4db7e9f55d4c9df3551f29ddf1c4a18dcc9dd54"}, - {file = "jiter-0.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3b2763996167830889a854b4ded30bb90897f9b76be78069c50c3ec4540950e"}, - {file = "jiter-0.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:675e8ab98c99495091af6b6e9bf2b6353bcf81f25ab6ce27d36127e315b4505d"}, - {file = "jiter-0.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e48e43d9d999aaf55f53406b8846ff8cbe3e47ee4b9dc37e5a10a65ce760809f"}, - {file = "jiter-0.4.2-cp310-none-win32.whl", hash = "sha256:881b6e67c50bc36acb3570eda693763c8cd77d590940e06fa6d325d0da52ec1b"}, - {file = "jiter-0.4.2-cp310-none-win_amd64.whl", hash = "sha256:bb8f7b43259efc6add0d721ade2953e064b24e2026d26d979bc09ec080844cef"}, - {file = "jiter-0.4.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:24ad336ac47f274fa83f6fbedcabff9d3387c80f67c66b992688e6a8ba2c47e9"}, - {file = "jiter-0.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fc392a220095730afe365ce1516f2f88bb085a2fd29ea191be9c6e3c71713d9a"}, - {file = "jiter-0.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1fdc408de36c81460896de0176f2f7b9f3574dcd35693a0b2c00f4ca34c98e4"}, - {file = "jiter-0.4.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c10ad76722ee6a8c820b0db06a793c08b7d679e5201b9563015bd1e06c959a09"}, - {file = "jiter-0.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb46d1e9c82bba87f0cbda38413e49448a7df35b1e55917124bff9f38974a23"}, - {file = "jiter-0.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:194e28ef4b5f3b61408cb2ee6b6dcbcdb0c9063d01b92b01345b7605692849f5"}, - {file = "jiter-0.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0a447533eccd62748a727e058efa10a8d7cf1de8ffe1a4d705ecb41dad9090"}, - {file = "jiter-0.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5f7704d7260bbb88cca3453951af739589132b26e896a3144fa2dae2263716d7"}, - {file = "jiter-0.4.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:01427458bc9550f2eda09d425755330e7d0eb09adce099577433bebf05d28d59"}, - {file = "jiter-0.4.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:159b8416879c0053b17c352f70b67b749ef5b2924c6154318ecf71918aab0905"}, - {file = "jiter-0.4.2-cp311-none-win32.whl", hash = "sha256:f2445234acfb79048ce1a0d5d0e181abb9afd9e4a29d8d9988fe26cc5773a81a"}, - {file = "jiter-0.4.2-cp311-none-win_amd64.whl", hash = "sha256:e15a65f233b6b0e5ac10ddf3b97ceb18aa9ffba096259961641d78b4ee321bd5"}, - {file = "jiter-0.4.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d61d59521aea9745447ce50f74d39a16ef74ec9d6477d9350d77e75a3d774ad2"}, - {file = "jiter-0.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eef607dc0acc251923427808dbd017f1998ae3c1a0430a261527aa5cbb3a942"}, - {file = "jiter-0.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af6bf39954646e374fc47429c656372ac731a6a26b644158a5a84bcdbed33a47"}, - {file = "jiter-0.4.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f509d23606e476852ee46a2b65b5c4ad3905f17424d9cc19c1dffa1c94ba3c6"}, - {file = "jiter-0.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59672774daa44ee140aada0c781c82bee4d9ac5e522966186cfb6b3c217d8a51"}, - {file = "jiter-0.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a0458efac5afeca254cf557b8a654e17013075a69905c78f88d557f129d871"}, - {file = "jiter-0.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8860766d1c293e75c1bb4e25b74fa987e3adf199cac3f5f9e6e49c2bebf092f"}, - {file = "jiter-0.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a109f3281b72bbf4921fe43db1005c004a38559ca0b6c4985add81777dfe0a44"}, - {file = "jiter-0.4.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:faa7e667454b77ad2f0ef87db39f4944de759617aadf210ea2b73f26bb24755f"}, - {file = "jiter-0.4.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3512f8b00cafb6780b427cb6282800d2bf8277161d9c917830661bd4ed1d3528"}, - {file = "jiter-0.4.2-cp312-none-win32.whl", hash = "sha256:853b35d508ee5b66d06630473c1c0b7bb5e29bf4785c9d2202437116c94f7e21"}, - {file = "jiter-0.4.2-cp312-none-win_amd64.whl", hash = "sha256:4a3a8197784278eb8b24cb02c45e1cad67c2ce5b5b758adfb19b87f74bbdff9c"}, - {file = "jiter-0.4.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ca2a4d750aed3154b89f2efb148609fc985fad8db739460797aaf9b478acedda"}, - {file = "jiter-0.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0e6c304b3cc6896256727e1fb8991c7179a345eca8224e201795e9cacf4683b0"}, - {file = "jiter-0.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cc34ac708ae1750d077e490321761ec4b9a055b994cbdd1d6fbd37099e4aa7b"}, - {file = "jiter-0.4.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c93383875ab8d2e4f760aaff335b4a12ff32d4f9cf49c4498d657734f611466"}, - {file = "jiter-0.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce197ee044add576afca0955b42142dd0312639adb6ebadbdbe4277f2855614f"}, - {file = "jiter-0.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a427716813ff65480ca5b5117cfa099f49b49cd38051f8609bd0d5493013ca0"}, - {file = "jiter-0.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479990218353356234669e70fac53e5eb6f739a10db25316171aede2c97d9364"}, - {file = "jiter-0.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d35a91ec5ac74cf33234c431505299fa91c0a197c2dbafd47400aca7c69489d4"}, - {file = "jiter-0.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b27189847193708c94ad10ca0d891309342ae882725d2187cf5d2db02bde8d1b"}, - {file = "jiter-0.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76c255308cd1093fb411a03756b7bb220e48d4a98c30cbc79ed448bf3978e27d"}, - {file = "jiter-0.4.2-cp38-none-win32.whl", hash = "sha256:bb77438060bad49cc251941e6701b31138365c8a0ddaf10cdded2fcc6dd30701"}, - {file = "jiter-0.4.2-cp38-none-win_amd64.whl", hash = "sha256:ce858af19f7ce0d4b51c9f6c0c9d08f1e9dcef1986c5875efd0674a7054292ca"}, - {file = "jiter-0.4.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:6128838a2f357b3921b2a3242d5dc002ae4255ecc8f9f05c20d56d7d2d79c5ad"}, - {file = "jiter-0.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f2420cebb9ba856cb57dcab1d2d8def949b464b0db09c22a4e4dbd52fff7b200"}, - {file = "jiter-0.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d13d8128e853b320e00bb18bd4bb8b136cc0936091dc87633648fc688eb705"}, - {file = "jiter-0.4.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eba5d6e54f149c508ba88677f97d3dc7dd75e9980d234bbac8027ac6db0763a3"}, - {file = "jiter-0.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fad5d64af0bc0545237419bf4150d8de56f0bd217434bdd1a59730327252bef"}, - {file = "jiter-0.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d179e7bca89cf5719bd761dd37a341ff0f98199ecaa9c14af09792e47e977cc"}, - {file = "jiter-0.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36353caee9f103d8ee7bda077f6400505b0f370e27eabcab33a33d21de12a2a6"}, - {file = "jiter-0.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd146c25bce576ca5db64fc7eccb8862af00f1f0e30108796953f12a53660e4c"}, - {file = "jiter-0.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:14b7c08cadbcd703041c66dc30e24e17de2f340281cac0e69374223ecf153aa4"}, - {file = "jiter-0.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a90f1a8b3d29aea198f8ea2b01148276ced8056e5103f32525266b3d880e65c9"}, - {file = "jiter-0.4.2-cp39-none-win32.whl", hash = "sha256:25b174997c780337b61ae57b1723455eecae9a17a9659044fd3c3b369190063f"}, - {file = "jiter-0.4.2-cp39-none-win_amd64.whl", hash = "sha256:bef62cea18521c5b99368147040c7e560c55098a35c93456f110678a2d34189a"}, - {file = "jiter-0.4.2.tar.gz", hash = "sha256:29b9d44f23f0c05f46d482f4ebf03213ee290d77999525d0975a17f875bf1eea"}, -] -markers = {main = "extra == \"openai\""} - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - -[[package]] -name = "joblib" -version = "1.4.2" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, - {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "2.4" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] -files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, -] -markers = {main = "extra == \"langchain\""} - -[[package]] -name = "kubernetes" -version = "29.0.0" -description = "Kubernetes python client" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "kubernetes-29.0.0-py2.py3-none-any.whl", hash = "sha256:ab8cb0e0576ccdfb71886366efb102c6a20f268d817be065ce7f9909c631e43e"}, - {file = "kubernetes-29.0.0.tar.gz", hash = "sha256:c4812e227ae74d07d53c88293e564e54b850452715a59a927e7e1bc6b9a60459"}, -] - -[package.dependencies] -certifi = ">=14.05.14" -google-auth = ">=1.0.1" -oauthlib = ">=3.2.2" -python-dateutil = ">=2.5.3" -pyyaml = ">=5.4.1" -requests = "*" -requests-oauthlib = "*" -six = ">=1.9.0" -urllib3 = ">=1.24.2" -websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" - -[package.extras] -adal = ["adal (>=1.0.2)"] - -[[package]] -name = "langchain" -version = "0.3.12" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] -files = [ - {file = "langchain-0.3.12-py3-none-any.whl", hash = "sha256:581ad93a9de12e4b957bc2af9ba8482eb86e3930e84c4ee20ed677da5e2311cd"}, - {file = "langchain-0.3.12.tar.gz", hash = "sha256:0d8247afbf37beb263b4adc29f7aa8a5ae83c43a6941894e2f9ba39d5c869e3b"}, -] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -aiohttp = ">=3.8.3,<4.0.0" -async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.3.25,<0.4.0" -langchain-text-splitters = ">=0.3.3,<0.4.0" -langsmith = ">=0.1.17,<0.3" -numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, - {version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""}, -] -pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" - -[[package]] -name = "langchain-anthropic" -version = "0.3.0" -description = "An integration package connecting AnthropicMessages and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_anthropic-0.3.0-py3-none-any.whl", hash = "sha256:96b74a9adfcc092cc2ae137d4189ca50e8f5ad9635618024f7c98d8f9fc1076a"}, - {file = "langchain_anthropic-0.3.0.tar.gz", hash = "sha256:f9b5cbdbf2d5b3432f78f056e474efb10a2c1e37f9a471d3aceb50a0d9f945df"}, -] - -[package.dependencies] -anthropic = ">=0.39.0,<1" -defusedxml = ">=0.7.1,<0.8.0" -langchain-core = ">=0.3.17,<0.4.0" +async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} +langchain-core = ">=0.3.72,<1.0.0" +langchain-text-splitters = ">=0.3.9,<1.0.0" +langsmith = ">=0.1.17" pydantic = ">=2.7.4,<3.0.0" - -[[package]] -name = "langchain-aws" -version = "0.2.9" -description = "An integration package connecting AWS and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_aws-0.2.9-py3-none-any.whl", hash = "sha256:e6b70017e731d44c3c24c90025b7b199e524c042ae8f8be7094d8bd34ae3adb3"}, - {file = "langchain_aws-0.2.9.tar.gz", hash = "sha256:bcc1d4c62addf4ee6cca4e441c63269003e1485069193991e27cae5579bc0353"}, -] - -[package.dependencies] -boto3 = ">=1.35.74" -langchain-core = ">=0.3.15,<0.4" -numpy = [ - {version = ">=1,<2", markers = "python_version < \"3.12\""}, - {version = ">=1.26.0,<3", markers = "python_version >= \"3.12\""}, -] -pydantic = ">=2,<3" - -[[package]] -name = "langchain-cohere" -version = "0.3.3" -description = "An integration package connecting Cohere and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_cohere-0.3.3-py3-none-any.whl", hash = "sha256:c8dee47a31cedb227ccf3ba93dad5f09ebadf9043e0ce941ae0bffdc3a226b37"}, - {file = "langchain_cohere-0.3.3.tar.gz", hash = "sha256:502f35eb5f983656b26114c7411628241fd06f14e24c85721ea57c9ee1c7c890"}, -] - -[package.dependencies] -cohere = ">=5.5.6,<6.0" -langchain-core = ">=0.3.0,<0.4" -langchain-experimental = ">=0.3.0,<0.4.0" -pandas = ">=1.4.3" -pydantic = ">=2,<3" -tabulate = ">=0.9.0,<0.10.0" - -[package.extras] -langchain-community = ["langchain-community (>=0.3.0,<0.4.0)"] - -[[package]] -name = "langchain-community" -version = "0.3.12" -description = "Community contributed LangChain integrations." -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_community-0.3.12-py3-none-any.whl", hash = "sha256:5a993c931d46dc07fcdfcdfa4d87095c5a15d37ff32b0c16e9ecf6f5caa58c9c"}, - {file = "langchain_community-0.3.12.tar.gz", hash = "sha256:b4694f34c7214dede03fe5a75e9f335e16bd788dfa6ca279302ad357bf0d0fc4"}, -] - -[package.dependencies] -aiohttp = ">=3.8.3,<4.0.0" -dataclasses-json = ">=0.5.7,<0.7" -httpx-sse = ">=0.4.0,<0.5.0" -langchain = ">=0.3.12,<0.4.0" -langchain-core = ">=0.3.25,<0.4.0" -langsmith = ">=0.1.125,<0.3" -numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, - {version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""}, -] -pydantic-settings = ">=2.4.0,<3.0.0" PyYAML = ">=5.3" requests = ">=2,<3" SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" + +[package.extras] +anthropic = ["langchain-anthropic"] +aws = ["langchain-aws"] +azure-ai = ["langchain-azure-ai"] +cohere = ["langchain-cohere"] +community = ["langchain-community"] +deepseek = ["langchain-deepseek"] +fireworks = ["langchain-fireworks"] +google-genai = ["langchain-google-genai"] +google-vertexai = ["langchain-google-vertexai"] +groq = ["langchain-groq"] +huggingface = ["langchain-huggingface"] +mistralai = ["langchain-mistralai"] +ollama = ["langchain-ollama"] +openai = ["langchain-openai"] +perplexity = ["langchain-perplexity"] +together = ["langchain-together"] +xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "0.3.25" +version = "0.3.74" description = "Building applications with LLMs through composability" optional = false -python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "langchain_core-0.3.25-py3-none-any.whl", hash = "sha256:e10581c6c74ba16bdc6fdf16b00cced2aa447cc4024ed19746a1232918edde38"}, - {file = "langchain_core-0.3.25.tar.gz", hash = "sha256:fdb8df41e5cdd928c0c2551ebbde1cea770ee3c64598395367ad77ddf9acbae7"}, + {file = "langchain_core-0.3.74-py3-none-any.whl", hash = "sha256:088338b5bc2f6a66892f9afc777992c24ee3188f41cbc603d09181e34a228ce7"}, + {file = "langchain_core-0.3.74.tar.gz", hash = "sha256:ff604441aeade942fbcc0a3860a592daba7671345230c2078ba2eb5f82b6ba76"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.125,<0.3" -packaging = ">=23.2,<25" -pydantic = [ - {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] +langsmith = ">=0.3.45" +packaging = ">=23.2" +pydantic = ">=2.7.4" PyYAML = ">=5.3" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" -[[package]] -name = "langchain-experimental" -version = "0.3.3" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_experimental-0.3.3-py3-none-any.whl", hash = "sha256:da01aafc162631475f306ca368ecae74d5becd93b8039bddb6315e755e274580"}, - {file = "langchain_experimental-0.3.3.tar.gz", hash = "sha256:6bbcdcd084581432ef4b5d732294a59d75a858ede1714b50a5b79bcfe31fa306"}, -] - -[package.dependencies] -langchain-community = ">=0.3.0,<0.4.0" -langchain-core = ">=0.3.15,<0.4.0" - -[[package]] -name = "langchain-google-vertexai" -version = "2.0.9" -description = "An integration package connecting Google VertexAI and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_google_vertexai-2.0.9-py3-none-any.whl", hash = "sha256:cb1085cf75793bd03fc825363f2cad9706eb1c30fd263da1998098e4c01e9c8f"}, - {file = "langchain_google_vertexai-2.0.9.tar.gz", hash = "sha256:da4a2c08d84a392fa6ab09d8f3f45eb5e86fd5e063ed7b4085c9f62fb04dc1ca"}, -] - -[package.dependencies] -google-cloud-aiplatform = ">=1.70.0,<2.0.0" -google-cloud-storage = ">=2.18.0,<3.0.0" -httpx = ">=0.27.0,<0.28.0" -httpx-sse = ">=0.4.0,<0.5.0" -langchain-core = ">=0.3.15,<0.4" -pydantic = ">=2.9,<2.10" - -[package.extras] -anthropic = ["anthropic[vertexai] (>=0.35.0,<1)"] -mistral = ["langchain-mistralai (>=0.2.0,<1)"] - -[[package]] -name = "langchain-groq" -version = "0.2.1" -description = "An integration package connecting Groq and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_groq-0.2.1-py3-none-any.whl", hash = "sha256:98d282fd9d7d99b0f55de0a1daea2d5d350ef697e3cb5e97de06aeba4eca8679"}, - {file = "langchain_groq-0.2.1.tar.gz", hash = "sha256:a59c81d1a15dc97abf4fdb4c2589f98109313eda147e6b378829222d4d929792"}, -] - -[package.dependencies] -groq = ">=0.4.1,<1" -langchain-core = ">=0.3.15,<0.4.0" - -[[package]] -name = "langchain-mistralai" -version = "0.2.3" -description = "An integration package connecting Mistral and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_mistralai-0.2.3-py3-none-any.whl", hash = "sha256:960abdd540af527da7e4d68b9316355ece6a740a60d145f32e4e19c1a9fe4d3a"}, - {file = "langchain_mistralai-0.2.3.tar.gz", hash = "sha256:2033af92fd82ac7915664885a94579edec19feb3122525fff8dd2787e174c087"}, -] - -[package.dependencies] -httpx = ">=0.25.2,<1" -httpx-sse = ">=0.3.1,<1" -langchain-core = ">=0.3.21,<0.4.0" -pydantic = ">=2,<3" -tokenizers = ">=0.15.1,<1" - -[[package]] -name = "langchain-ollama" -version = "0.2.1" -description = "An integration package connecting Ollama and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_ollama-0.2.1-py3-none-any.whl", hash = "sha256:033916150cc9c341d72274512b9987a0ebf014cf808237687012fc7af4a81ee3"}, - {file = "langchain_ollama-0.2.1.tar.gz", hash = "sha256:752b112d233a6e079259cb10138a5af836f42d26781cac6d7eb1b1e0d2ae9a0d"}, -] - -[package.dependencies] -langchain-core = ">=0.3.20,<0.4.0" -ollama = ">=0.3.0,<1" - [[package]] name = "langchain-openai" -version = "0.2.11" +version = "0.2.14" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_openai-0.2.11-py3-none-any.whl", hash = "sha256:c019ae915a5782943bee9503388e65c8622d400e0451ef885f3e4989cf35727f"}, - {file = "langchain_openai-0.2.11.tar.gz", hash = "sha256:563bd843092d260c7ffd88b8e0e6b830f36347e058e62a6d5e9cc4c461a8da98"}, -] - -[package.dependencies] -langchain-core = ">=0.3.21,<0.4.0" -openai = ">=1.54.0,<2.0.0" -tiktoken = ">=0.7,<1" - -[[package]] -name = "langchain-text-splitters" -version = "0.3.3" -description = "LangChain text splitting utilities" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] -files = [ - {file = "langchain_text_splitters-0.3.3-py3-none-any.whl", hash = "sha256:c2f8650457685072971edc8c52c9f8826496b3307f28004a7fd09eb32d4d819f"}, - {file = "langchain_text_splitters-0.3.3.tar.gz", hash = "sha256:c596958dcab15fdfe0627fd36ce9d588d0a7e35593af70cd10d0a4a06d69b3ee"}, -] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -langchain-core = ">=0.3.25,<0.4.0" - -[[package]] -name = "langgraph" -version = "0.2.62" -description = "Building stateful, multi-actor applications with LLMs" -optional = false -python-versions = "<4.0,>=3.9.0" -groups = ["dev"] -files = [ - {file = "langgraph-0.2.62-py3-none-any.whl", hash = "sha256:51ae9e02a52485a837642eebe7ae43269af7d7305d62f8f69ac11589b2fbba26"}, - {file = "langgraph-0.2.62.tar.gz", hash = "sha256:0aac9fd55ffe669bc1312203e0f9ea2733c65cc276f196e7ff0d443cf4efbb89"}, -] - -[package.dependencies] -langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.3.15 || >0.3.15,<0.3.16 || >0.3.16,<0.3.17 || >0.3.17,<0.3.18 || >0.3.18,<0.3.19 || >0.3.19,<0.3.20 || >0.3.20,<0.3.21 || >0.3.21,<0.3.22 || >0.3.22,<0.4.0" -langgraph-checkpoint = ">=2.0.4,<3.0.0" -langgraph-sdk = ">=0.1.42,<0.2.0" - -[[package]] -name = "langgraph-checkpoint" -version = "2.0.9" -description = "Library with base interfaces for LangGraph checkpoint savers." -optional = false -python-versions = "<4.0.0,>=3.9.0" -groups = ["dev"] -files = [ - {file = "langgraph_checkpoint-2.0.9-py3-none-any.whl", hash = "sha256:b546ed6129929b8941ac08af6ce5cd26c8ebe1d25883d3c48638d34ade91ce42"}, - {file = "langgraph_checkpoint-2.0.9.tar.gz", hash = "sha256:43847d7e385a2d9d2b684155920998e44ed42d2d1780719e4f6111fe3d6db84c"}, -] - -[package.dependencies] -langchain-core = ">=0.2.38,<0.4" -msgpack = ">=1.1.0,<2.0.0" - -[[package]] -name = "langgraph-sdk" -version = "0.1.51" -description = "SDK for interacting with LangGraph API" -optional = false -python-versions = "<4.0.0,>=3.9.0" -groups = ["dev"] -files = [ - {file = "langgraph_sdk-0.1.51-py3-none-any.whl", hash = "sha256:ce2b58466d1700d06149782ed113157a8694a6d7932c801f316cd13fab315fe4"}, - {file = "langgraph_sdk-0.1.51.tar.gz", hash = "sha256:dea1363e72562cb1e82a2d156be8d5b1a69ff3fe8815eee0e1e7a2f423242ec1"}, -] - -[package.dependencies] -httpx = ">=0.25.2" -orjson = ">=3.10.1" - -[[package]] -name = "langsmith" -version = "0.1.146" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.8.1" -groups = ["main", "dev"] -files = [ - {file = "langsmith-0.1.146-py3-none-any.whl", hash = "sha256:9d062222f1a32c9b047dab0149b24958f988989cd8d4a5f9139ff959a51e59d8"}, - {file = "langsmith-0.1.146.tar.gz", hash = "sha256:ead8b0b9d5b6cd3ac42937ec48bdf09d4afe7ca1bba22dc05eb65591a18106f8"}, -] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -httpx = ">=0.23.0,<1" -orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} -pydantic = [ - {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -requests = ">=2,<3" -requests-toolbelt = ">=1.0.0,<2.0.0" - -[[package]] -name = "lark" -version = "1.1.9" -description = "a modern parsing library" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db"}, - {file = "lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba"}, -] - -[package.extras] -atomic-cache = ["atomicwrites"] -interegular = ["interegular (>=0.3.1,<0.4.0)"] -nearley = ["js2py"] -regex = ["regex"] - -[[package]] -name = "llama-index-core" -version = "0.12.41" -description = "Interface between LLMs and your data" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "llama_index_core-0.12.41-py3-none-any.whl", hash = "sha256:61fc1e912303250ee32778d5db566e242f7bd9ee043940860a436e3c93f17ef5"}, - {file = "llama_index_core-0.12.41.tar.gz", hash = "sha256:34f77c51b12b11ee1d743ff183c1b61afe2975f9970357723a744d4e080f5b3d"}, -] - -[package.dependencies] -aiohttp = ">=3.8.6,<4" -aiosqlite = "*" -banks = ">=2.0.0,<3" -dataclasses-json = "*" -deprecated = ">=1.2.9.3" -dirtyjson = ">=1.0.8,<2" -eval-type-backport = {version = ">=0.2.0,<0.3", markers = "python_version < \"3.10\""} -filetype = ">=1.2.0,<2" -fsspec = ">=2023.5.0" -httpx = "*" -nest-asyncio = ">=1.5.8,<2" -networkx = ">=3.0" -nltk = ">3.8.1" -numpy = "*" -pillow = ">=9.0.0" -pydantic = ">=2.8.0" -pyyaml = ">=6.0.1" -requests = ">=2.31.0" -sqlalchemy = {version = ">=1.4.49", extras = ["asyncio"]} -tenacity = ">=8.2.0,<8.4.0 || >8.4.0,<10.0.0" -tiktoken = ">=0.7.0" -tqdm = ">=4.66.1,<5" -typing-extensions = ">=4.5.0" -typing-inspect = ">=0.8.0" -wrapt = "*" - -[[package]] -name = "llama-index-llms-anthropic" -version = "0.5.0" -description = "llama-index llms anthropic integration" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "llama_index_llms_anthropic-0.5.0-py3-none-any.whl", hash = "sha256:2b9367db45deabcbda4db1b1216c95e2663e1e6f129570fc2d275207dd3901cf"}, - {file = "llama_index_llms_anthropic-0.5.0.tar.gz", hash = "sha256:14e400ccc2deb8e9024ef8cdc24550b67f4240f3563b4564d4870be85de2d9d8"}, -] - -[package.dependencies] -anthropic = {version = ">=0.39.0", extras = ["bedrock", "vertex"]} -llama-index-core = ">=0.12.0,<0.13.0" - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -groups = ["dev", "docs"] -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "marshmallow" -version = "3.21.2" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, - {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mmh3" -version = "4.1.0" -description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5ac76a8b0cd8095784e51e4c1c9c318c19edcd1709a06eb14979c8d850c31a"}, - {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98a49121afdfab67cd80e912b36404139d7deceb6773a83620137aaa0da5714c"}, - {file = "mmh3-4.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5259ac0535874366e7d1a5423ef746e0d36a9e3c14509ce6511614bdc5a7ef5b"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5950827ca0453a2be357696da509ab39646044e3fa15cad364eb65d78797437"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dd0f652ae99585b9dd26de458e5f08571522f0402155809fd1dc8852a613a39"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d25548070942fab1e4a6f04d1626d67e66d0b81ed6571ecfca511f3edf07e6"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53db8d9bad3cb66c8f35cbc894f336273f63489ce4ac416634932e3cbe79eb5b"}, - {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75da0f615eb55295a437264cc0b736753f830b09d102aa4c2a7d719bc445ec05"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b926b07fd678ea84b3a2afc1fa22ce50aeb627839c44382f3d0291e945621e1a"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c5b053334f9b0af8559d6da9dc72cef0a65b325ebb3e630c680012323c950bb6"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bf33dc43cd6de2cb86e0aa73a1cc6530f557854bbbe5d59f41ef6de2e353d7b"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fa7eacd2b830727ba3dd65a365bed8a5c992ecd0c8348cf39a05cc77d22f4970"}, - {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42dfd6742b9e3eec599f85270617debfa0bbb913c545bb980c8a4fa7b2d047da"}, - {file = "mmh3-4.1.0-cp310-cp310-win32.whl", hash = "sha256:2974ad343f0d39dcc88e93ee6afa96cedc35a9883bc067febd7ff736e207fa47"}, - {file = "mmh3-4.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:74699a8984ded645c1a24d6078351a056f5a5f1fe5838870412a68ac5e28d865"}, - {file = "mmh3-4.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f0dc874cedc23d46fc488a987faa6ad08ffa79e44fb08e3cd4d4cf2877c00a00"}, - {file = "mmh3-4.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3280a463855b0eae64b681cd5b9ddd9464b73f81151e87bb7c91a811d25619e6"}, - {file = "mmh3-4.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97ac57c6c3301769e757d444fa7c973ceb002cb66534b39cbab5e38de61cd896"}, - {file = "mmh3-4.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b6502cdb4dbd880244818ab363c8770a48cdccecf6d729ade0241b736b5ec0"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ba2da04671a9621580ddabf72f06f0e72c1c9c3b7b608849b58b11080d8f14"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a5fef4c4ecc782e6e43fbeab09cff1bac82c998a1773d3a5ee6a3605cde343e"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5135358a7e00991f73b88cdc8eda5203bf9de22120d10a834c5761dbeb07dd13"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cff9ae76a54f7c6fe0167c9c4028c12c1f6de52d68a31d11b6790bb2ae685560"}, - {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f02576a4d106d7830ca90278868bf0983554dd69183b7bbe09f2fcd51cf54f"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:073d57425a23721730d3ff5485e2da489dd3c90b04e86243dd7211f889898106"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:71e32ddec7f573a1a0feb8d2cf2af474c50ec21e7a8263026e8d3b4b629805db"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7cbb20b29d57e76a58b40fd8b13a9130db495a12d678d651b459bf61c0714cea"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a42ad267e131d7847076bb7e31050f6c4378cd38e8f1bf7a0edd32f30224d5c9"}, - {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a013979fc9390abadc445ea2527426a0e7a4495c19b74589204f9b71bcaafeb"}, - {file = "mmh3-4.1.0-cp311-cp311-win32.whl", hash = "sha256:1d3b1cdad7c71b7b88966301789a478af142bddcb3a2bee563f7a7d40519a00f"}, - {file = "mmh3-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dc6dc32eb03727467da8e17deffe004fbb65e8b5ee2b502d36250d7a3f4e2ec"}, - {file = "mmh3-4.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9ae3a5c1b32dda121c7dc26f9597ef7b01b4c56a98319a7fe86c35b8bc459ae6"}, - {file = "mmh3-4.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0033d60c7939168ef65ddc396611077a7268bde024f2c23bdc283a19123f9e9c"}, - {file = "mmh3-4.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6af3e2287644b2b08b5924ed3a88c97b87b44ad08e79ca9f93d3470a54a41c5"}, - {file = "mmh3-4.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d82eb4defa245e02bb0b0dc4f1e7ee284f8d212633389c91f7fba99ba993f0a2"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba245e94b8d54765e14c2d7b6214e832557e7856d5183bc522e17884cab2f45d"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb04e2feeabaad6231e89cd43b3d01a4403579aa792c9ab6fdeef45cc58d4ec0"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3b1a27def545ce11e36158ba5d5390cdbc300cfe456a942cc89d649cf7e3b2"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce0ab79ff736d7044e5e9b3bfe73958a55f79a4ae672e6213e92492ad5e734d5"}, - {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b02268be6e0a8eeb8a924d7db85f28e47344f35c438c1e149878bb1c47b1cd3"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:deb887f5fcdaf57cf646b1e062d56b06ef2f23421c80885fce18b37143cba828"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99dd564e9e2b512eb117bd0cbf0f79a50c45d961c2a02402787d581cec5448d5"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:08373082dfaa38fe97aa78753d1efd21a1969e51079056ff552e687764eafdfe"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:54b9c6a2ea571b714e4fe28d3e4e2db37abfd03c787a58074ea21ee9a8fd1740"}, - {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a7b1edf24c69e3513f879722b97ca85e52f9032f24a52284746877f6a7304086"}, - {file = "mmh3-4.1.0-cp312-cp312-win32.whl", hash = "sha256:411da64b951f635e1e2284b71d81a5a83580cea24994b328f8910d40bed67276"}, - {file = "mmh3-4.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bebc3ecb6ba18292e3d40c8712482b4477abd6981c2ebf0e60869bd90f8ac3a9"}, - {file = "mmh3-4.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:168473dd608ade6a8d2ba069600b35199a9af837d96177d3088ca91f2b3798e3"}, - {file = "mmh3-4.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:372f4b7e1dcde175507640679a2a8790185bb71f3640fc28a4690f73da986a3b"}, - {file = "mmh3-4.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:438584b97f6fe13e944faf590c90fc127682b57ae969f73334040d9fa1c7ffa5"}, - {file = "mmh3-4.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6e27931b232fc676675fac8641c6ec6b596daa64d82170e8597f5a5b8bdcd3b6"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:571a92bad859d7b0330e47cfd1850b76c39b615a8d8e7aa5853c1f971fd0c4b1"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a69d6afe3190fa08f9e3a58e5145549f71f1f3fff27bd0800313426929c7068"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afb127be0be946b7630220908dbea0cee0d9d3c583fa9114a07156f98566dc28"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:940d86522f36348ef1a494cbf7248ab3f4a1638b84b59e6c9e90408bd11ad729"}, - {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dcccc4935686619a8e3d1f7b6e97e3bd89a4a796247930ee97d35ea1a39341"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01bb9b90d61854dfc2407c5e5192bfb47222d74f29d140cb2dd2a69f2353f7cc"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bcb1b8b951a2c0b0fb8a5426c62a22557e2ffc52539e0a7cc46eb667b5d606a9"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6477a05d5e5ab3168e82e8b106e316210ac954134f46ec529356607900aea82a"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:da5892287e5bea6977364b15712a2573c16d134bc5fdcdd4cf460006cf849278"}, - {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:99180d7fd2327a6fffbaff270f760576839dc6ee66d045fa3a450f3490fda7f5"}, - {file = "mmh3-4.1.0-cp38-cp38-win32.whl", hash = "sha256:9b0d4f3949913a9f9a8fb1bb4cc6ecd52879730aab5ff8c5a3d8f5b593594b73"}, - {file = "mmh3-4.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:598c352da1d945108aee0c3c3cfdd0e9b3edef74108f53b49d481d3990402169"}, - {file = "mmh3-4.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:475d6d1445dd080f18f0f766277e1237fa2914e5fe3307a3b2a3044f30892103"}, - {file = "mmh3-4.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5ca07c41e6a2880991431ac717c2a049056fff497651a76e26fc22224e8b5732"}, - {file = "mmh3-4.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ebe052fef4bbe30c0548d12ee46d09f1b69035ca5208a7075e55adfe091be44"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaefd42e85afb70f2b855a011f7b4d8a3c7e19c3f2681fa13118e4d8627378c5"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0ae43caae5a47afe1b63a1ae3f0986dde54b5fb2d6c29786adbfb8edc9edfb"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6218666f74c8c013c221e7f5f8a693ac9cf68e5ac9a03f2373b32d77c48904de"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac59294a536ba447b5037f62d8367d7d93b696f80671c2c45645fa9f1109413c"}, - {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086844830fcd1e5c84fec7017ea1ee8491487cfc877847d96f86f68881569d2e"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e42b38fad664f56f77f6fbca22d08450f2464baa68acdbf24841bf900eb98e87"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d08b790a63a9a1cde3b5d7d733ed97d4eb884bfbc92f075a091652d6bfd7709a"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:73ea4cc55e8aea28c86799ecacebca09e5f86500414870a8abaedfcbaf74d288"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f90938ff137130e47bcec8dc1f4ceb02f10178c766e2ef58a9f657ff1f62d124"}, - {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa1f13e94b8631c8cd53259250556edcf1de71738936b60febba95750d9632bd"}, - {file = "mmh3-4.1.0-cp39-cp39-win32.whl", hash = "sha256:a3b680b471c181490cf82da2142029edb4298e1bdfcb67c76922dedef789868d"}, - {file = "mmh3-4.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:fefef92e9c544a8dbc08f77a8d1b6d48006a750c4375bbcd5ff8199d761e263b"}, - {file = "mmh3-4.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:8e2c1f6a2b41723a4f82bd5a762a777836d29d664fc0095f17910bea0adfd4a6"}, - {file = "mmh3-4.1.0.tar.gz", hash = "sha256:a1cf25348b9acd229dda464a094d6170f47d2850a1fcb762a3b6172d2ce6ca4a"}, -] - -[package.extras] -test = ["mypy (>=1.0)", "pytest (>=7.0.0)"] - -[[package]] -name = "monotonic" -version = "1.6" -description = "An implementation of time.monotonic() for Python 2 & < 3.3" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, - {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -description = "Python library for arbitrary-precision floating-point arithmetic" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, - {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, -] - -[package.extras] -develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] -docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] -tests = ["pytest (>=4.6)"] - -[[package]] -name = "msgpack" -version = "1.1.0" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, - {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, - {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, - {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, - {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, - {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, - {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, - {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, - {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, - {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, - {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, - {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, - {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, - {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, -] - -[[package]] -name = "multidict" -version = "6.0.5" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, -] -markers = {main = "extra == \"langchain\""} - -[[package]] -name = "mypy" -version = "1.16.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, - {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, - {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, - {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, - {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, - {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, - {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, - {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, - {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, - {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, - {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, - {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, - {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, - {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, - {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, - {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, - {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, - {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, - {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, - {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, - {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, - {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, - {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, - {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, - {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, - {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, - {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, - {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, - {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, - {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, - {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, - {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, + {file = "langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8"}, + {file = "langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978"}, ] -[package.dependencies] -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] +[package.dependencies] +langchain-core = ">=0.3.27,<0.4.0" +openai = ">=1.58.1,<2.0.0" +tiktoken = ">=0.7,<1" [[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["dev"] +name = "langchain-text-splitters" +version = "0.3.9" +description = "LangChain text splitting utilities" +optional = true +python-versions = ">=3.9" files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, + {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, ] +[package.dependencies] +langchain-core = ">=0.3.72,<1.0.0" + [[package]] -name = "nest-asyncio" -version = "1.6.0" -description = "Patch asyncio to allow nested event loops" +name = "langgraph" +version = "0.2.76" +description = "Building stateful, multi-actor applications with LLMs" optional = false -python-versions = ">=3.5" -groups = ["dev"] +python-versions = "<4.0,>=3.9.0" files = [ - {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, - {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, + {file = "langgraph-0.2.76-py3-none-any.whl", hash = "sha256:076b8b5d2fc5a9761c46a7618430cfa5c978a8012257c43cbc127b27e0fd7872"}, + {file = "langgraph-0.2.76.tar.gz", hash = "sha256:688f8dcd9b6797ba78384599e0de944773000c75156ad1e186490e99e89fa5c0"}, ] +[package.dependencies] +langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.3.15 || >0.3.15,<0.3.16 || >0.3.16,<0.3.17 || >0.3.17,<0.3.18 || >0.3.18,<0.3.19 || >0.3.19,<0.3.20 || >0.3.20,<0.3.21 || >0.3.21,<0.3.22 || >0.3.22,<0.4.0" +langgraph-checkpoint = ">=2.0.10,<3.0.0" +langgraph-sdk = ">=0.1.42,<0.2.0" + [[package]] -name = "networkx" -version = "3.1" -description = "Python package for creating and manipulating graphs and networks" +name = "langgraph-checkpoint" +version = "2.1.1" +description = "Library with base interfaces for LangGraph checkpoint savers." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, - {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, + {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, + {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, ] -[package.extras] -default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] -developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] -test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] +[package.dependencies] +langchain-core = ">=0.2.38" +ormsgpack = ">=1.10.0" [[package]] -name = "nltk" -version = "3.9.1" -description = "Natural Language Toolkit" +name = "langgraph-sdk" +version = "0.1.74" +description = "SDK for interacting with LangGraph API" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, - {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, + {file = "langgraph_sdk-0.1.74-py3-none-any.whl", hash = "sha256:3a265c3757fe0048adad4391d10486db63ef7aa5a2cbd22da22d4503554cb890"}, + {file = "langgraph_sdk-0.1.74.tar.gz", hash = "sha256:7450e0db5b226cc2e5328ca22c5968725873630ef47c4206a30707cb25dc3ad6"}, ] [package.dependencies] -click = "*" -joblib = "*" -regex = ">=2021.8.3" -tqdm = "*" - -[package.extras] -all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] -corenlp = ["requests"] -machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] -plot = ["matplotlib"] -tgrep = ["pyparsing"] -twitter = ["twython"] +httpx = ">=0.25.2" +orjson = ">=3.10.1" [[package]] -name = "nodeenv" -version = "1.8.0" -description = "Node.js virtual environment builder" +name = "langsmith" +version = "0.4.13" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "langsmith-0.4.13-py3-none-any.whl", hash = "sha256:dab7b16ee16986995007bf5a777f45c18f8bf7453f67ae2ebcb46ce43c214297"}, + {file = "langsmith-0.4.13.tar.gz", hash = "sha256:1ae7dbb5d8150647406f49885a2dd16ab12bd990254b5dc23718838b3d086fde"}, ] [package.dependencies] -setuptools = "*" +httpx = ">=0.23.0,<1" +orjson = {version = ">=3.9.14", markers = "platform_python_implementation != \"PyPy\""} +packaging = ">=23.2" +pydantic = ">=1,<3" +requests = ">=2.0.0" +requests-toolbelt = ">=1.0.0" +zstandard = ">=0.23.0" + +[package.extras] +langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] +openai-agents = ["openai-agents (>=0.0.3)"] +otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] +pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4)", "vcrpy (>=7.0.0)"] +vcr = ["vcrpy (>=7.0.0)"] [[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] -markers = {main = "extra == \"langchain\""} [[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +name = "mypy" +version = "1.17.1" +description = "Optional static typing for Python" optional = false -python-versions = ">=3.6" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + [package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] [[package]] -name = "ollama" -version = "0.4.1" -description = "The official Python client for Ollama." +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = "<4.0,>=3.8" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "ollama-0.4.1-py3-none-any.whl", hash = "sha256:b6fb16aa5a3652633e1716acb12cf2f44aa18beb229329e46a0302734822dfad"}, - {file = "ollama-0.4.1.tar.gz", hash = "sha256:8c6b5e7ff80dd0b8692150b03359f60bac7ca162b088c604069409142a684ad3"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] -[package.dependencies] -httpx = ">=0.27.0,<0.28.0" -pydantic = ">=2.9.0,<3.0.0" - [[package]] -name = "onnxruntime" -version = "1.17.3" -description = "ONNX Runtime is a runtime accelerator for Machine Learning models" +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" optional = false -python-versions = "*" -groups = ["dev"] +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "onnxruntime-1.17.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d86dde9c0bb435d709e51bd25991c9fe5b9a5b168df45ce119769edc4d198b15"}, - {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d87b68bf931ac527b2d3c094ead66bb4381bac4298b65f46c54fe4d1e255865"}, - {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e950cf0333cf114a155f9142e71da344d2b08dfe202763a403ae81cc02ebd1"}, - {file = "onnxruntime-1.17.3-cp310-cp310-win32.whl", hash = "sha256:0962a4d0f5acebf62e1f0bf69b6e0adf16649115d8de854c1460e79972324d68"}, - {file = "onnxruntime-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:468ccb8a0faa25c681a41787b1594bf4448b0252d3efc8b62fd8b2411754340f"}, - {file = "onnxruntime-1.17.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e8cd90c1c17d13d47b89ab076471e07fb85467c01dcd87a8b8b5cdfbcb40aa51"}, - {file = "onnxruntime-1.17.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a058b39801baefe454eeb8acf3ada298c55a06a4896fafc224c02d79e9037f60"}, - {file = "onnxruntime-1.17.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f823d5eb4807007f3da7b27ca972263df6a1836e6f327384eb266274c53d05d"}, - {file = "onnxruntime-1.17.3-cp311-cp311-win32.whl", hash = "sha256:b66b23f9109e78ff2791628627a26f65cd335dcc5fbd67ff60162733a2f7aded"}, - {file = "onnxruntime-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:570760ca53a74cdd751ee49f13de70d1384dcf73d9888b8deac0917023ccda6d"}, - {file = "onnxruntime-1.17.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:77c318178d9c16e9beadd9a4070d8aaa9f57382c3f509b01709f0f010e583b99"}, - {file = "onnxruntime-1.17.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23da8469049b9759082e22c41a444f44a520a9c874b084711b6343672879f50b"}, - {file = "onnxruntime-1.17.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2949730215af3f9289008b2e31e9bbef952012a77035b911c4977edea06f3f9e"}, - {file = "onnxruntime-1.17.3-cp312-cp312-win32.whl", hash = "sha256:6c7555a49008f403fb3b19204671efb94187c5085976ae526cb625f6ede317bc"}, - {file = "onnxruntime-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:58672cf20293a1b8a277a5c6c55383359fcdf6119b2f14df6ce3b140f5001c39"}, - {file = "onnxruntime-1.17.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4395ba86e3c1e93c794a00619ef1aec597ab78f5a5039f3c6d2e9d0695c0a734"}, - {file = "onnxruntime-1.17.3-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf354c04344ec38564fc22394e1fe08aa6d70d790df00159205a0055c4a4d3f"}, - {file = "onnxruntime-1.17.3-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a94b600b7af50e922d44b95a57981e3e35103c6e3693241a03d3ca204740bbda"}, - {file = "onnxruntime-1.17.3-cp38-cp38-win32.whl", hash = "sha256:5a335c76f9c002a8586c7f38bc20fe4b3725ced21f8ead835c3e4e507e42b2ab"}, - {file = "onnxruntime-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f56a86fbd0ddc8f22696ddeda0677b041381f4168a2ca06f712ef6ec6050d6d"}, - {file = "onnxruntime-1.17.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:e0ae39f5452278cd349520c296e7de3e90d62dc5b0157c6868e2748d7f28b871"}, - {file = "onnxruntime-1.17.3-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ff2dc012bd930578aff5232afd2905bf16620815f36783a941aafabf94b3702"}, - {file = "onnxruntime-1.17.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf6c37483782e4785019b56e26224a25e9b9a35b849d0169ce69189867a22bb1"}, - {file = "onnxruntime-1.17.3-cp39-cp39-win32.whl", hash = "sha256:351bf5a1140dcc43bfb8d3d1a230928ee61fcd54b0ea664c8e9a889a8e3aa515"}, - {file = "onnxruntime-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:57a3de15778da8d6cc43fbf6cf038e1e746146300b5f0b1fbf01f6f795dc6440"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -coloredlogs = "*" -flatbuffers = "*" -numpy = ">=1.21.6" -packaging = "*" -protobuf = "*" -sympy = "*" - [[package]] name = "openai" -version = "1.93.0" +version = "1.99.5" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "openai-1.93.0-py3-none-any.whl", hash = "sha256:3d746fe5498f0dd72e0d9ab706f26c91c0f646bf7459e5629af8ba7c9dbdf090"}, - {file = "openai-1.93.0.tar.gz", hash = "sha256:988f31ade95e1ff0585af11cc5a64510225e4f5cd392698c675d0a9265b8e337"}, + {file = "openai-1.99.5-py3-none-any.whl", hash = "sha256:4e870f9501b7c36132e2be13313ce3c4d6915a837e7a299c483aab6a6d4412e9"}, + {file = "openai-1.99.5.tar.gz", hash = "sha256:aa97ac3326cac7949c5e4ac0274c454c1d19c939760107ae0d3948fc26a924ca"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -3264,396 +1065,302 @@ tqdm = ">4" typing-extensions = ">=4.11,<5" [package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.6)"] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.8)"] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] [[package]] name = "opentelemetry-api" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_api-1.33.1-py3-none-any.whl", hash = "sha256:4db83ebcf7ea93e64637ec6ee6fabee45c5cbe4abd9cf3da95c43828ddb50b83"}, - {file = "opentelemetry_api-1.33.1.tar.gz", hash = "sha256:1c6055fc0a2d3f23a50c7e17e16ef75ad489345fd3df1f8b8af7c0bbf8a109e8"}, + {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, + {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, ] [package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<8.7.0" +importlib-metadata = ">=6.0,<8.8.0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-exporter-otlp" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Collector Exporters" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_exporter_otlp-1.33.1-py3-none-any.whl", hash = "sha256:9bcf1def35b880b55a49e31ebd63910edac14b294fd2ab884953c4deaff5b300"}, - {file = "opentelemetry_exporter_otlp-1.33.1.tar.gz", hash = "sha256:4d050311ea9486e3994575aa237e32932aad58330a31fba24fdba5c0d531cf04"}, + {file = "opentelemetry_exporter_otlp-1.36.0-py3-none-any.whl", hash = "sha256:de93b7c45bcc78296998775d52add7c63729e83ef2cd6560730a6b336d7f6494"}, + {file = "opentelemetry_exporter_otlp-1.36.0.tar.gz", hash = "sha256:72f166ea5a8923ac42889337f903e93af57db8893de200369b07401e98e4e06b"}, ] [package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.33.1" -opentelemetry-exporter-otlp-proto-http = "1.33.1" +opentelemetry-exporter-otlp-proto-grpc = "1.36.0" +opentelemetry-exporter-otlp-proto-http = "1.36.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.33.1-py3-none-any.whl", hash = "sha256:b81c1de1ad349785e601d02715b2d29d6818aed2c809c20219f3d1f20b038c36"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.33.1.tar.gz", hash = "sha256:c57b3fa2d0595a21c4ed586f74f948d259d9949b58258f11edb398f246bec131"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, ] [package.dependencies] -opentelemetry-proto = "1.33.1" +opentelemetry-proto = "1.36.0" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.33.1-py3-none-any.whl", hash = "sha256:7e8da32c7552b756e75b4f9e9c768a61eb47dee60b6550b37af541858d669ce1"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.33.1.tar.gz", hash = "sha256:345696af8dc19785fac268c8063f3dc3d5e274c774b308c634f39d9c21955728"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf"}, ] [package.dependencies] -deprecated = ">=1.2.6" -googleapis-common-protos = ">=1.52,<2.0" +googleapis-common-protos = ">=1.57,<2.0" grpcio = [ {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, {version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""}, ] opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.33.1" -opentelemetry-proto = "1.33.1" -opentelemetry-sdk = ">=1.33.1,<1.34.0" +opentelemetry-exporter-otlp-proto-common = "1.36.0" +opentelemetry-proto = "1.36.0" +opentelemetry-sdk = ">=1.36.0,<1.37.0" +typing-extensions = ">=4.6.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.33.1-py3-none-any.whl", hash = "sha256:ebd6c523b89a2ecba0549adb92537cc2bf647b4ee61afbbd5a4c6535aa3da7cf"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.33.1.tar.gz", hash = "sha256:46622d964a441acb46f463ebdc26929d9dec9efb2e54ef06acdc7305e8593c38"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, ] [package.dependencies] -deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.33.1" -opentelemetry-proto = "1.33.1" -opentelemetry-sdk = ">=1.33.1,<1.34.0" +opentelemetry-exporter-otlp-proto-common = "1.36.0" +opentelemetry-proto = "1.36.0" +opentelemetry-sdk = ">=1.36.0,<1.37.0" requests = ">=2.7,<3.0" - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.54b1" -description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "opentelemetry_instrumentation-0.54b1-py3-none-any.whl", hash = "sha256:a4ae45f4a90c78d7006c51524f57cd5aa1231aef031eae905ee34d5423f5b198"}, - {file = "opentelemetry_instrumentation-0.54b1.tar.gz", hash = "sha256:7658bf2ff914b02f246ec14779b66671508125c0e4227361e56b5ebf6cef0aec"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.54b1" -packaging = ">=18.0" -wrapt = ">=1.0.0,<2.0.0" - -[[package]] -name = "opentelemetry-instrumentation-asgi" -version = "0.54b1" -description = "ASGI instrumentation for OpenTelemetry" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "opentelemetry_instrumentation_asgi-0.54b1-py3-none-any.whl", hash = "sha256:84674e822b89af563b283a5283c2ebb9ed585d1b80a1c27fb3ac20b562e9f9fc"}, - {file = "opentelemetry_instrumentation_asgi-0.54b1.tar.gz", hash = "sha256:ab4df9776b5f6d56a78413c2e8bbe44c90694c67c844a1297865dc1bd926ed3c"}, -] - -[package.dependencies] -asgiref = ">=3.0,<4.0" -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.54b1" -opentelemetry-semantic-conventions = "0.54b1" -opentelemetry-util-http = "0.54b1" - -[package.extras] -instruments = ["asgiref (>=3.0,<4.0)"] - -[[package]] -name = "opentelemetry-instrumentation-fastapi" -version = "0.54b1" -description = "OpenTelemetry FastAPI Instrumentation" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "opentelemetry_instrumentation_fastapi-0.54b1-py3-none-any.whl", hash = "sha256:fb247781cfa75fd09d3d8713c65e4a02bd1e869b00e2c322cc516d4b5429860c"}, - {file = "opentelemetry_instrumentation_fastapi-0.54b1.tar.gz", hash = "sha256:1fcad19cef0db7092339b571a59e6f3045c9b58b7fd4670183f7addc459d78df"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.54b1" -opentelemetry-instrumentation-asgi = "0.54b1" -opentelemetry-semantic-conventions = "0.54b1" -opentelemetry-util-http = "0.54b1" - -[package.extras] -instruments = ["fastapi (>=0.58,<1.0)"] +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-proto" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_proto-1.33.1-py3-none-any.whl", hash = "sha256:243d285d9f29663fc7ea91a7171fcc1ccbbfff43b48df0774fd64a37d98eda70"}, - {file = "opentelemetry_proto-1.33.1.tar.gz", hash = "sha256:9627b0a5c90753bf3920c398908307063e4458b287bb890e5c1d6fa11ad50b68"}, + {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, + {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, ] [package.dependencies] -protobuf = ">=5.0,<6.0" +protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.33.1" +version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_sdk-1.33.1-py3-none-any.whl", hash = "sha256:19ea73d9a01be29cacaa5d6c8ce0adc0b7f7b4d58cc52f923e4413609f670112"}, - {file = "opentelemetry_sdk-1.33.1.tar.gz", hash = "sha256:85b9fcf7c3d23506fbc9692fd210b8b025a1920535feec50bd54ce203d57a531"}, + {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, + {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, ] [package.dependencies] -opentelemetry-api = "1.33.1" -opentelemetry-semantic-conventions = "0.54b1" -typing-extensions = ">=3.7.4" +opentelemetry-api = "1.36.0" +opentelemetry-semantic-conventions = "0.57b0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.54b1" +version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "opentelemetry_semantic_conventions-0.54b1-py3-none-any.whl", hash = "sha256:29dab644a7e435b58d3a3918b58c333c92686236b30f7891d5e51f02933ca60d"}, - {file = "opentelemetry_semantic_conventions-0.54b1.tar.gz", hash = "sha256:d1cecedae15d19bdaafca1e56b29a66aa286f50b5d08f036a145c7f3e9ef9cee"}, + {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, + {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, ] [package.dependencies] -deprecated = ">=1.2.6" -opentelemetry-api = "1.33.1" - -[[package]] -name = "opentelemetry-util-http" -version = "0.54b1" -description = "Web util for OpenTelemetry" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "opentelemetry_util_http-0.54b1-py3-none-any.whl", hash = "sha256:b1c91883f980344a1c3c486cffd47ae5c9c1dd7323f9cbe9fdb7cadb401c87c9"}, - {file = "opentelemetry_util_http-0.54b1.tar.gz", hash = "sha256:f0b66868c19fbaf9c9d4e11f4a7599fa15d5ea50b884967a26ccd9d72c7c9d15"}, -] +opentelemetry-api = "1.36.0" +typing-extensions = ">=4.5.0" [[package]] name = "orjson" -version = "3.10.2" +version = "3.11.1" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "orjson-3.10.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:87124c1b3471a072fda422e156dd7ef086d854937d68adc266f17f32a1043c95"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1b79526bd039e775ad0f558800c3cd9f3bde878a1268845f63984d37bcbb5d1"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f6dc97a6b2833a0d77598e7d016b6d964e4b0bc9576c89aa9a16fcf8ac902d"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e427ce004fe15e13dcfdbd6c9dc936abf83d85d2164ec415a8bd90954f6f781"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f3e05f70ab6225ba38504a2be61935d6ebc09de2b1bc484c30cb96ca4fa24b8"}, - {file = "orjson-3.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4e67821e3c1f0ec5dbef9dbd0bc9cd0fe4f0d8ba5d76a07038ee3843c9ac98a"}, - {file = "orjson-3.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24877561fe96a3736224243d6e2e026a674a4ddeff2b02fdeac41801bd261c87"}, - {file = "orjson-3.10.2-cp310-none-win32.whl", hash = "sha256:5da4ce52892b00aa51f5c5781414dc2bcdecc8470d2d60eeaeadbc14c5d9540b"}, - {file = "orjson-3.10.2-cp310-none-win_amd64.whl", hash = "sha256:cee3df171d957e84f568c3920f1f077f7f2a69f8ce4303d4c1404b7aab2f365a"}, - {file = "orjson-3.10.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a361e7ad84452416a469cdda7a2efeee8ddc9e06e4b95938b072045e205f86dc"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b064251af6a2b7fb26e51b9abd3c1e615b53d5d5f87972263233d66d9c736a4"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:464c30c24961cc83b2dc0e5532ed41084624ee1c71d4e7ef1aaec88f7a677393"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4459005982748fda9871f04bce6a304c515afc46c96bef51e2bc81755c0f4ea0"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd0cd3a113a6ea0051c4a50cca65161ee50c014a01363554a1417d9f3c4529f"}, - {file = "orjson-3.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a658ebc5143fbc0a9e3a10aafce4de50b01b1b0a41942038cb4bc6617f1e1d7"}, - {file = "orjson-3.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2fa4addaf6a6b3eb836cf92c4986d5ef9215fbdc87e4891cf8fd97990972bba0"}, - {file = "orjson-3.10.2-cp311-none-win32.whl", hash = "sha256:faff04363bfcff9cb41ab09c0ce8db84b8d4a09a374305ec5b12210dfa3154ea"}, - {file = "orjson-3.10.2-cp311-none-win_amd64.whl", hash = "sha256:7aee7b31a6acecf65a94beef2191081692891b00e8b7e02fbcc0c85002d62d0b"}, - {file = "orjson-3.10.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:38d9e9eab01131fdccbe95bff4f1d8ea197d239b5c73396e2079d07730bfa205"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfd84ecf5ebe8ec334a95950427e7ade40135032b1f00e2b17f351b0ef6dc72b"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2ba009d85c3c98006759e62150d018d622aa79012fdeefbb70a42a542582b45"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eac25b54fab6d9ccbf9dbc57555c2b52bf6d0802ea84bd2bd9670a161bd881dc"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e735d90a90caf746de59becf29642c8358cafcd9b1a906ae3566efcc495324"}, - {file = "orjson-3.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:12feeee9089654904c2c988788eb9d521f5752c83ea410969d1f58d05ea95943"}, - {file = "orjson-3.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:619a7a4df76497afd2e6f1c963cc7e13658b3d58425c3a2ccf0471ad61d71025"}, - {file = "orjson-3.10.2-cp312-none-win32.whl", hash = "sha256:460d221090b451a0e78813196ec9dd28d2e33103048cfd7c1a3312a532fe3b1f"}, - {file = "orjson-3.10.2-cp312-none-win_amd64.whl", hash = "sha256:7efa93a9540e6ac9fe01167389fd7b1f0250cbfe3a8f06fe23e045d2a2d5d6ac"}, - {file = "orjson-3.10.2-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ceb283b8c048fb20bd1c703b10e710783a4f1ba7d5654358a25db99e9df94d5"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201bf2b96ba39941254ef6b02e080660861e1444ec50be55778e1c38446c2d39"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51a7b67c8cddf1a9de72d534244590103b1f17b2105d3bdcb221981bd97ab427"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde123c227e28ef9bba7092dc88abbd1933a0d7c17c58970c8ed8ec804e7add5"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b51caf8720b6df448acf764312d4678aeed6852ebfa6f3aa28b6061155ffef"}, - {file = "orjson-3.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f124d7e813e7b3d56bb7841d3d0884fec633f5f889a27a158d004b6b37e5ca98"}, - {file = "orjson-3.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e33ac7a6b081688a2167b501c9813aa6ec1f2cc097c47ab5f33cca3e875da9dc"}, - {file = "orjson-3.10.2-cp38-none-win32.whl", hash = "sha256:8f4a91921270d646f50f90a9903f87baae24c6e376ef3c275fcd0ffc051117bb"}, - {file = "orjson-3.10.2-cp38-none-win_amd64.whl", hash = "sha256:148d266e300257ff6d8e8a5895cc1e12766b8db676510b4f1d79b0d07f666fdd"}, - {file = "orjson-3.10.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:27158a75e7239145cf385d2318fdb27fbcd1fc494a470ee68287147c8b214cb1"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26302b13e3f542b3e1ad1723e3543caf28e2f372391d21e1642de29c06e6209"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:712cb3aa976311ae53de116a64949392aa5e7dcceda6769d5d7169d303d5ed09"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9db3e6f23a6c9ce6c883a8e10e0eae0e2895327fb6e2286019b13153e59c672f"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44787769d93d1ef9f25a80644ef020e0f30f37045d6336133e421a414c8fe51"}, - {file = "orjson-3.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53a43b18d280c8d18cb18437921a05ec478b908809f9e89ad60eb2fdf0ba96ac"}, - {file = "orjson-3.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99e270b6a13027ed4c26c2b75b06c2cfb950934c8eb0400d70f4e6919bfe24f4"}, - {file = "orjson-3.10.2-cp39-none-win32.whl", hash = "sha256:d6f71486d211db9a01094cdd619ab594156a43ca04fa24e23ee04dac1509cdca"}, - {file = "orjson-3.10.2-cp39-none-win_amd64.whl", hash = "sha256:161f3b4e6364132562af80967ac3211e6681d320a01954da4915af579caab0b2"}, - {file = "orjson-3.10.2.tar.gz", hash = "sha256:47affe9f704c23e49a0fbb9d441af41f602474721e8639e8814640198f9ae32f"}, -] -markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} - -[[package]] -name = "overrides" -version = "7.7.0" -description = "A decorator to automatically detect mismatch when overriding a method." + {file = "orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5caf7f13f2e1b4e137060aed892d4541d07dabc3f29e6d891e2383c7ed483440"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f716bcc166524eddfcf9f13f8209ac19a7f27b05cf591e883419079d98c8c99d"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507d6012fab05465d8bf21f5d7f4635ba4b6d60132874e349beff12fb51af7fe"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1545083b0931f754c80fd2422a73d83bea7a6d1b6de104a5f2c8dd3d64c291e"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e217ce3bad76351e1eb29ebe5ca630326f45cd2141f62620107a229909501a3"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ef26e009304bda4df42e4afe518994cde6f89b4b04c0ff24021064f83f4fbb"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ba49683b87bea3ae1489a88e766e767d4f423a669a61270b6d6a7ead1c33bd65"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5072488fcc5cbcda2ece966d248e43ea1d222e19dd4c56d3f82747777f24d864"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f58ae2bcd119226fe4aa934b5880fe57b8e97b69e51d5d91c88a89477a307016"}, + {file = "orjson-3.11.1-cp310-cp310-win32.whl", hash = "sha256:6723be919c07906781b9c63cc52dc7d2fb101336c99dd7e85d3531d73fb493f7"}, + {file = "orjson-3.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:5fd44d69ddfdfb4e8d0d83f09d27a4db34930fba153fbf79f8d4ae8b47914e04"}, + {file = "orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef"}, + {file = "orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55"}, + {file = "orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17"}, + {file = "orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313"}, + {file = "orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af"}, + {file = "orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910"}, + {file = "orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4"}, + {file = "orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf"}, + {file = "orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae"}, + {file = "orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d"}, + {file = "orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e"}, + {file = "orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f"}, + {file = "orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092"}, + {file = "orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77"}, + {file = "orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4"}, + {file = "orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b"}, + {file = "orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e"}, + {file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727"}, + {file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45"}, + {file = "orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1"}, + {file = "orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8"}, + {file = "orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1"}, + {file = "orjson-3.11.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3d593a9e0bccf2c7401ae53625b519a7ad7aa555b1c82c0042b322762dc8af4e"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baad413c498fc1eef568504f11ea46bc71f94b845c075e437da1e2b85b4fb86"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22cf17ae1dae3f9b5f37bfcdba002ed22c98bbdb70306e42dc18d8cc9b50399a"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e855c1e97208133ce88b3ef6663c9a82ddf1d09390cd0856a1638deee0390c3c"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5861c5f7acff10599132854c70ab10abf72aebf7c627ae13575e5f20b1ab8fe"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1e6415c5b5ff3a616a6dafad7b6ec303a9fc625e9313c8e1268fb1370a63dcb"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912579642f5d7a4a84d93c5eed8daf0aa34e1f2d3f4dc6571a8e418703f5701e"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2092e1d3b33f64e129ff8271642afddc43763c81f2c30823b4a4a4a5f2ea5b55"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:b8ac64caba1add2c04e9cd4782d4d0c4d6c554b7a3369bdec1eed7854c98db7b"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:23196b826ebc85c43f8e27bee0ab33c5fb13a29ea47fb4fcd6ebb1e660eb0252"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f2d3364cfad43003f1e3d564a069c8866237cca30f9c914b26ed2740b596ed00"}, + {file = "orjson-3.11.1-cp39-cp39-win32.whl", hash = "sha256:20b0dca94ea4ebe4628330de50975b35817a3f52954c1efb6d5d0498a3bbe581"}, + {file = "orjson-3.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:200c3ad7ed8b5d31d49143265dfebd33420c4b61934ead16833b5cd2c3d241be"}, + {file = "orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96"}, +] + +[[package]] +name = "ormsgpack" +version = "1.10.0" +description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.6" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, - {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, + {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, + {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, + {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7058ef6092f995561bf9f71d6c9a4da867b6cc69d2e94cb80184f579a3ceed5"}, + {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6f3509c1b0e51b15552d314b1d409321718122e90653122ce4b997f01453a"}, + {file = "ormsgpack-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c1edafd5c72b863b1f875ec31c529f09c872a5ff6fe473b9dfaf188ccc3227"}, + {file = "ormsgpack-1.10.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c780b44107a547a9e9327270f802fa4d6b0f6667c9c03c3338c0ce812259a0f7"}, + {file = "ormsgpack-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:137aab0d5cdb6df702da950a80405eb2b7038509585e32b4e16289604ac7cb84"}, + {file = "ormsgpack-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:3e666cb63030538fa5cd74b1e40cb55b6fdb6e2981f024997a288bf138ebad07"}, + {file = "ormsgpack-1.10.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4bb7df307e17b36cbf7959cd642c47a7f2046ae19408c564e437f0ec323a7775"}, + {file = "ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8817ae439c671779e1127ee62f0ac67afdeaeeacb5f0db45703168aa74a2e4af"}, + {file = "ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f345f81e852035d80232e64374d3a104139d60f8f43c6c5eade35c4bac5590e"}, + {file = "ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21de648a1c7ef692bdd287fb08f047bd5371d7462504c0a7ae1553c39fee35e3"}, + {file = "ormsgpack-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3a7d844ae9cbf2112c16086dd931b2acefce14cefd163c57db161170c2bfa22b"}, + {file = "ormsgpack-1.10.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4d80585403d86d7f800cf3d0aafac1189b403941e84e90dd5102bb2b92bf9d5"}, + {file = "ormsgpack-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:da1de515a87e339e78a3ccf60e39f5fb740edac3e9e82d3c3d209e217a13ac08"}, + {file = "ormsgpack-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:57c4601812684024132cbb32c17a7d4bb46ffc7daf2fddf5b697391c2c4f142a"}, + {file = "ormsgpack-1.10.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4e159d50cd4064d7540e2bc6a0ab66eab70b0cc40c618b485324ee17037527c0"}, + {file = "ormsgpack-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb47c85f3a866e29279d801115b554af0fefc409e2ed8aa90aabfa77efe5cc6"}, + {file = "ormsgpack-1.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c28249574934534c9bd5dce5485c52f21bcea0ee44d13ece3def6e3d2c3798b5"}, + {file = "ormsgpack-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1957dcadbb16e6a981cd3f9caef9faf4c2df1125e2a1b702ee8236a55837ce07"}, + {file = "ormsgpack-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b29412558c740bf6bac156727aa85ac67f9952cd6f071318f29ee72e1a76044"}, + {file = "ormsgpack-1.10.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6933f350c2041ec189fe739f0ba7d6117c8772f5bc81f45b97697a84d03020dd"}, + {file = "ormsgpack-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a86de06d368fcc2e58b79dece527dc8ca831e0e8b9cec5d6e633d2777ec93d0"}, + {file = "ormsgpack-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:35fa9f81e5b9a0dab42e09a73f7339ecffdb978d6dbf9deb2ecf1e9fc7808722"}, + {file = "ormsgpack-1.10.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d816d45175a878993b7372bd5408e0f3ec5a40f48e2d5b9d8f1cc5d31b61f1f"}, + {file = "ormsgpack-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90345ccb058de0f35262893751c603b6376b05f02be2b6f6b7e05d9dd6d5643"}, + {file = "ormsgpack-1.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144b5e88f1999433e54db9d637bae6fe21e935888be4e3ac3daecd8260bd454e"}, + {file = "ormsgpack-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2190b352509d012915921cca76267db136cd026ddee42f1b0d9624613cc7058c"}, + {file = "ormsgpack-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:86fd9c1737eaba43d3bb2730add9c9e8b5fbed85282433705dd1b1e88ea7e6fb"}, + {file = "ormsgpack-1.10.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:33afe143a7b61ad21bb60109a86bb4e87fec70ef35db76b89c65b17e32da7935"}, + {file = "ormsgpack-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f23d45080846a7b90feabec0d330a9cc1863dc956728412e4f7986c80ab3a668"}, + {file = "ormsgpack-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:534d18acb805c75e5fba09598bf40abe1851c853247e61dda0c01f772234da69"}, + {file = "ormsgpack-1.10.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:efdb25cf6d54085f7ae557268d59fd2d956f1a09a340856e282d2960fe929f32"}, + {file = "ormsgpack-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddfcb30d4b1be2439836249d675f297947f4fb8efcd3eeb6fd83021d773cadc4"}, + {file = "ormsgpack-1.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee0944b6ccfd880beb1ca29f9442a774683c366f17f4207f8b81c5e24cadb453"}, + {file = "ormsgpack-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35cdff6a0d3ba04e40a751129763c3b9b57a602c02944138e4b760ec99ae80a1"}, + {file = "ormsgpack-1.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:599ccdabc19c618ef5de6e6f2e7f5d48c1f531a625fa6772313b8515bc710681"}, + {file = "ormsgpack-1.10.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:bf46f57da9364bd5eefd92365c1b78797f56c6f780581eecd60cd7b367f9b4d3"}, + {file = "ormsgpack-1.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b796f64fdf823dedb1e35436a4a6f889cf78b1aa42d3097c66e5adfd8c3bd72d"}, + {file = "ormsgpack-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:106253ac9dc08520951e556b3c270220fcb8b4fef0d30b71eedac4befa4de749"}, + {file = "ormsgpack-1.10.0.tar.gz", hash = "sha256:7f7a27efd67ef22d7182ec3b7fa7e9d147c3ad9be2a24656b23c989077e08b16"}, ] [[package]] name = "packaging" -version = "24.1" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pandas" -version = "2.0.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, - {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, - {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, - {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, - {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, - {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, - {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, - {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, - {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, - {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, - {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version == \"3.10\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.1" - -[package.extras] -all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] -aws = ["s3fs (>=2021.08.0)"] -clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] -compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] -computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2021.07.0)"] -gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] -hdf5 = ["tables (>=3.6.1)"] -html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] -mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] -spss = ["pyreadstat (>=1.1.2)"] -sql-other = ["SQLAlchemy (>=1.4.16)"] -test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.6.3)"] - -[[package]] -name = "parameterized" -version = "0.9.0" -description = "Parameterized testing with any Python test framework" -optional = false -python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, - {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] -[package.extras] -dev = ["jinja2"] - [[package]] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -3665,7 +1372,6 @@ version = "14.7.0" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"}, {file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"}, @@ -3679,171 +1385,46 @@ pygments = ">=2.12.0" [package.extras] dev = ["hypothesis", "mypy", "pdoc-pyo3-sample-library (==1.0.11)", "pygments (>=2.14.0)", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"] -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions ; python_version < \"3.10\""] -xmp = ["defusedxml"] - [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "posthog" -version = "3.5.0" -description = "Integrate PostHog into any python application." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "posthog-3.5.0-py2.py3-none-any.whl", hash = "sha256:3c672be7ba6f95d555ea207d4486c171d06657eb34b3ce25eb043bfe7b6b5b76"}, - {file = "posthog-3.5.0.tar.gz", hash = "sha256:8f7e3b2c6e8714d0c0c542a2109b83a7549f63b7113a133ab2763a89245ef2ef"}, -] - -[package.dependencies] -backoff = ">=1.10.0" -monotonic = ">=1.5" -python-dateutil = ">2.1" -requests = ">=2.7,<3.0" -six = ">=1.5" - -[package.extras] -dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"] -sentry = ["django", "sentry-sdk"] -test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest", "pytest-timeout"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.5.0" +version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, ] [package.dependencies] @@ -3853,484 +1434,203 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "propcache" -version = "0.2.0" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, -] -markers = {main = "extra == \"langchain\""} - -[[package]] -name = "proto-plus" -version = "1.26.1" -description = "Beautiful, Pythonic protocol buffers" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, - {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<7.0.0" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - [[package]] name = "protobuf" -version = "5.29.5" +version = "6.31.1" description = "" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079"}, - {file = "protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc"}, - {file = "protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671"}, - {file = "protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015"}, - {file = "protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61"}, - {file = "protobuf-5.29.5-cp38-cp38-win32.whl", hash = "sha256:ef91363ad4faba7b25d844ef1ada59ff1604184c0bcd8b39b8a6bef15e1af238"}, - {file = "protobuf-5.29.5-cp38-cp38-win_amd64.whl", hash = "sha256:7318608d56b6402d2ea7704ff1e1e4597bee46d760e7e4dd42a3d45e24b87f2e"}, - {file = "protobuf-5.29.5-cp39-cp39-win32.whl", hash = "sha256:6f642dc9a61782fa72b90878af134c5afe1917c89a568cd3476d758d3c3a0736"}, - {file = "protobuf-5.29.5-cp39-cp39-win_amd64.whl", hash = "sha256:470f3af547ef17847a28e1f47200a1cbf0ba3ff57b7de50d22776607cd2ea353"}, - {file = "protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5"}, - {file = "protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84"}, -] - -[[package]] -name = "pyasn1" -version = "0.6.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, + {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, + {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, + {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, + {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, + {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, + {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, + {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, ] [[package]] -name = "pyasn1-modules" -version = "0.4.0" -description = "A collection of ASN.1-based protocols modules" +name = "pycparser" +version = "2.22" +description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, - {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - [[package]] name = "pydantic" -version = "2.9.2" +version = "2.11.7" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, -] +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pydantic-settings" -version = "2.6.1" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"}, - {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" - -[package.extras] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - [[package]] name = "pygments" -version = "2.17.2" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" -groups = ["dev", "docs"] +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] -plugins = ["importlib-metadata ; python_version < \"3.8\""] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pymongo" -version = "4.7.3" -description = "Python driver for MongoDB " -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pymongo-4.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e9580b4537b3cc5d412070caabd1dabdf73fdce249793598792bac5782ecf2eb"}, - {file = "pymongo-4.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:517243b2b189c98004570dd8fc0e89b1a48363d5578b3b99212fa2098b2ea4b8"}, - {file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23b1e9dabd61da1c7deb54d888f952f030e9e35046cebe89309b28223345b3d9"}, - {file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03e0f9901ad66c6fb7da0d303461377524d61dab93a4e4e5af44164c5bb4db76"}, - {file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a870824aa54453aee030bac08c77ebcf2fe8999400f0c2a065bebcbcd46b7f8"}, - {file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd7b3d3f4261bddbb74a332d87581bc523353e62bb9da4027cc7340f6fcbebc"}, - {file = "pymongo-4.7.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d719a643ea6da46d215a3ba51dac805a773b611c641319558d8576cbe31cef8"}, - {file = "pymongo-4.7.3-cp310-cp310-win32.whl", hash = "sha256:d8b1e06f361f3c66ee694cb44326e1a2e4f93bc9c3a4849ae8547889fca71154"}, - {file = "pymongo-4.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:c450ab2f9397e2d5caa7fddeb4feb30bf719c47c13ae02c0bbb3b71bf4099c1c"}, - {file = "pymongo-4.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cc6459209e885ba097779eaa0fe7f2fa049db39ab43b1731cf8d065a4650e8"}, - {file = "pymongo-4.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e2287f1e2cc35e73cd74a4867e398a97962c5578a3991c730ef78d276ca8e46"}, - {file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413506bd48d8c31ee100645192171e4773550d7cb940b594d5175ac29e329ea1"}, - {file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cc1febf17646d52b7561caa762f60bdfe2cbdf3f3e70772f62eb624269f9c05"}, - {file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dfcf18a49955d50a16c92b39230bd0668ffc9c164ccdfe9d28805182b48fa72"}, - {file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89872041196c008caddf905eb59d3dc2d292ae6b0282f1138418e76f3abd3ad6"}, - {file = "pymongo-4.7.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3ed97b89de62ea927b672ad524de0d23f3a6b4a01c8d10e3d224abec973fbc3"}, - {file = "pymongo-4.7.3-cp311-cp311-win32.whl", hash = "sha256:d2f52b38151e946011d888a8441d3d75715c663fc5b41a7ade595e924e12a90a"}, - {file = "pymongo-4.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:4a4cc91c28e81c0ce03d3c278e399311b0af44665668a91828aec16527082676"}, - {file = "pymongo-4.7.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cb30c8a78f5ebaca98640943447b6a0afcb146f40b415757c9047bf4a40d07b4"}, - {file = "pymongo-4.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9cf2069f5d37c398186453589486ea98bb0312214c439f7d320593b61880dc05"}, - {file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3564f423958fced8a8c90940fd2f543c27adbcd6c7c6ed6715d847053f6200a0"}, - {file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a8af8a38fa6951fff73e6ff955a6188f829b29fed7c5a1b739a306b4aa56fe8"}, - {file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a0e81c8dba6d825272867d487f18764cfed3c736d71d7d4ff5b79642acbed42"}, - {file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88fc1d146feabac4385ea8ddb1323e584922922641303c8bf392fe1c36803463"}, - {file = "pymongo-4.7.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4225100b2c5d1f7393d7c5d256ceb8b20766830eecf869f8ae232776347625a6"}, - {file = "pymongo-4.7.3-cp312-cp312-win32.whl", hash = "sha256:5f3569ed119bf99c0f39ac9962fb5591eff02ca210fe80bb5178d7a1171c1b1e"}, - {file = "pymongo-4.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:eb383c54c0c8ba27e7712b954fcf2a0905fee82a929d277e2e94ad3a5ba3c7db"}, - {file = "pymongo-4.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a46cffe91912570151617d866a25d07b9539433a32231ca7e7cf809b6ba1745f"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c3cba427dac50944c050c96d958c5e643c33a457acee03bae27c8990c5b9c16"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a5fd893edbeb7fa982f8d44b6dd0186b6cd86c89e23f6ef95049ff72bffe46"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c168a2fadc8b19071d0a9a4f85fe38f3029fe22163db04b4d5c046041c0b14bd"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c59c2c9e70f63a7f18a31e367898248c39c068c639b0579623776f637e8f482"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08165fd82c89d372e82904c3268bd8fe5de44f92a00e97bb1db1785154397d9"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:397fed21afec4fdaecf72f9c4344b692e489756030a9c6d864393e00c7e80491"}, - {file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f903075f8625e2d228f1b9b9a0cf1385f1c41e93c03fd7536c91780a0fb2e98f"}, - {file = "pymongo-4.7.3-cp37-cp37m-win32.whl", hash = "sha256:8ed1132f58c38add6b6138b771d0477a3833023c015c455d9a6e26f367f9eb5c"}, - {file = "pymongo-4.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8d00a5d8fc1043a4f641cbb321da766699393f1b6f87c70fae8089d61c9c9c54"}, - {file = "pymongo-4.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9377b868c38700c7557aac1bc4baae29f47f1d279cc76b60436e547fd643318c"}, - {file = "pymongo-4.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da4a6a7b4f45329bb135aa5096823637bd5f760b44d6224f98190ee367b6b5dd"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:487e2f9277f8a63ac89335ec4f1699ae0d96ebd06d239480d69ed25473a71b2c"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db3d608d541a444c84f0bfc7bad80b0b897e0f4afa580a53f9a944065d9b633"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e90af2ad3a8a7c295f4d09a2fbcb9a350c76d6865f787c07fe843b79c6e821d1"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e28feb18dc559d50ededba27f9054c79f80c4edd70a826cecfe68f3266807b3"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f21ecddcba2d9132d5aebd8e959de8d318c29892d0718420447baf2b9bccbb19"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:26140fbb3f6a9a74bd73ed46d0b1f43d5702e87a6e453a31b24fad9c19df9358"}, - {file = "pymongo-4.7.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:94baa5fc7f7d22c3ce2ac7bd92f7e03ba7a6875f2480e3b97a400163d6eaafc9"}, - {file = "pymongo-4.7.3-cp38-cp38-win32.whl", hash = "sha256:92dd247727dd83d1903e495acc743ebd757f030177df289e3ba4ef8a8c561fad"}, - {file = "pymongo-4.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:1c90c848a5e45475731c35097f43026b88ef14a771dfd08f20b67adc160a3f79"}, - {file = "pymongo-4.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f598be401b416319a535c386ac84f51df38663f7a9d1071922bda4d491564422"}, - {file = "pymongo-4.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35ba90477fae61c65def6e7d09e8040edfdd3b7fd47c3c258b4edded60c4d625"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aa8735955c70892634d7e61b0ede9b1eefffd3cd09ccabee0ffcf1bdfe62254"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82a97d8f7f138586d9d0a0cff804a045cdbbfcfc1cd6bba542b151e284fbbec5"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de3b9db558930efab5eaef4db46dcad8bf61ac3ddfd5751b3e5ac6084a25e366"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0e149217ef62812d3c2401cf0e2852b0c57fd155297ecc4dcd67172c4eca402"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3a8a1ef4a824f5feb793b3231526d0045eadb5eb01080e38435dfc40a26c3e5"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d14e5e89a4be1f10efc3d9dcb13eb7a3b2334599cb6bb5d06c6a9281b79c8e22"}, - {file = "pymongo-4.7.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6bfa29f032fd4fd7b129520f8cdb51ab71d88c2ba0567cccd05d325f963acb5"}, - {file = "pymongo-4.7.3-cp39-cp39-win32.whl", hash = "sha256:1421d0bd2ce629405f5157bd1aaa9b83f12d53a207cf68a43334f4e4ee312b66"}, - {file = "pymongo-4.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:f7ee974f8b9370a998919c55b1050889f43815ab588890212023fecbc0402a6d"}, - {file = "pymongo-4.7.3.tar.gz", hash = "sha256:6354a66b228f2cd399be7429685fb68e07f19110a3679782ecb4fdb68da03831"}, -] - -[package.dependencies] -dnspython = ">=1.16.0,<3.0.0" - -[package.extras] -aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] -encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] -gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] -ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] -snappy = ["python-snappy"] -test = ["pytest (>=7)"] -zstd = ["zstandard"] - -[[package]] -name = "pypika" -version = "0.48.9" -description = "A SQL query builder API for Python" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378"}, -] - -[[package]] -name = "pyproject-hooks" -version = "1.1.0" -description = "Wrappers to call pyproject.toml-based build backend hooks." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"}, - {file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"}, -] - -[[package]] -name = "pyreadline3" -version = "3.4.1" -description = "A python implementation of GNU readline." -optional = false -python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, - {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, -] - [[package]] name = "pytest" -version = "8.3.4" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, - {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" pluggy = ">=1.5,<2" +pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" @@ -4338,7 +1638,6 @@ version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -4353,14 +1652,13 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-httpserver" -version = "1.0.12" +version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pytest_httpserver-1.0.12-py3-none-any.whl", hash = "sha256:dae1c79ec7aeda83bfaaf4d0a400867a4b1bc6bf668244daaf13aa814e3022da"}, - {file = "pytest_httpserver-1.0.12.tar.gz", hash = "sha256:c14600b8efb9ea8d7e63251a242ab987f13028b36d3d397ffaca3c929f67eb16"}, + {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, + {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, ] [package.dependencies] @@ -4368,14 +1666,13 @@ Werkzeug = ">=2.0.0" [[package]] name = "pytest-timeout" -version = "2.3.1" +version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, - {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, + {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, + {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, ] [package.dependencies] @@ -4383,14 +1680,13 @@ pytest = ">=7.0.0" [[package]] name = "pytest-xdist" -version = "3.6.1" +version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, ] [package.dependencies] @@ -4402,197 +1698,162 @@ psutil = ["psutil (>=3.0)"] setproctitle = ["setproctitle"] testing = ["filelock"] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["dev"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] -markers = {main = "extra == \"langchain\""} [[package]] name = "regex" -version = "2024.4.28" +version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"}, - {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"}, - {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"}, - {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"}, - {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"}, - {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"}, - {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"}, - {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"}, - {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"}, - {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"}, - {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"}, - {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95b4639c77d414efa93c8de14ce3f7965a94d007e068a94f9d4997bb9bd9c81f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7de1ceed5a5f84f342ba4a9f4ae589524adf9744b2ee61b5da884b5b659834"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02e5860a250cd350c4933cf376c3bc9cb28948e2c96a8bc042aee7b985cfa26f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a5966220b9a1a88691282b7e4350e9599cf65780ca60d914a798cb791aa1177"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48fb045bbd4aab2418dc1ba2088a5e32de4bfe64e1457b948bb328a8dc2f1c2e"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20ff8433fa45e131f7316594efe24d4679c5449c0ca69d91c2f9d21846fdf064"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c436fd1e95c04c19039668cfb548450a37c13f051e8659f40aed426e36b3765f"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b85241d3cfb9f8a13cefdfbd58a2843f208f2ed2c88181bf84e22e0c7fc066d"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:075641c94126b064c65ab86e7e71fc3d63e7ff1bea1fb794f0773c97cdad3a03"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70645cad3407d103d1dbcb4841839d2946f7d36cf38acbd40120fee1682151e5"}, + {file = "regex-2025.7.34-cp310-cp310-win32.whl", hash = "sha256:3b836eb4a95526b263c2a3359308600bd95ce7848ebd3c29af0c37c4f9627cd3"}, + {file = "regex-2025.7.34-cp310-cp310-win_amd64.whl", hash = "sha256:cbfaa401d77334613cf434f723c7e8ba585df162be76474bccc53ae4e5520b3a"}, + {file = "regex-2025.7.34-cp310-cp310-win_arm64.whl", hash = "sha256:bca11d3c38a47c621769433c47f364b44e8043e0de8e482c5968b20ab90a3986"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a"}, + {file = "regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1"}, + {file = "regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a"}, + {file = "regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282"}, + {file = "regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588"}, + {file = "regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62"}, + {file = "regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0"}, + {file = "regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1"}, + {file = "regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997"}, + {file = "regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e"}, + {file = "regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb"}, + {file = "regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae"}, + {file = "regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd5edc3f453de727af267c7909d083e19f6426fc9dd149e332b6034f2a5611e6"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa1cdfb8db96ef20137de5587954c812821966c3e8b48ffc871e22d7ec0a4938"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89c9504fc96268e8e74b0283e548f53a80c421182a2007e3365805b74ceef936"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33be70d75fa05a904ee0dc43b650844e067d14c849df7e82ad673541cd465b5f"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57d25b6732ea93eeb1d090e8399b6235ca84a651b52d52d272ed37d3d2efa0f1"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:baf2fe122a3db1c0b9f161aa44463d8f7e33eeeda47bb0309923deb743a18276"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a764a83128af9c1a54be81485b34dca488cbcacefe1e1d543ef11fbace191e1"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7f663ccc4093877f55b51477522abd7299a14c5bb7626c5238599db6a0cb95d"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4913f52fbc7a744aaebf53acd8d3dc1b519e46ba481d4d7596de3c862e011ada"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:efac4db9e044d47fd3b6b0d40b6708f4dfa2d8131a5ac1d604064147c0f552fd"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7373afae7cfb716e3b8e15d0184510d518f9d21471f2d62918dbece85f2c588f"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9960d162f3fecf6af252534a1ae337e9c2e20d74469fed782903b24e2cc9d3d7"}, + {file = "regex-2025.7.34-cp39-cp39-win32.whl", hash = "sha256:95d538b10eb4621350a54bf14600cc80b514211d91a019dc74b8e23d2159ace5"}, + {file = "regex-2025.7.34-cp39-cp39-win_amd64.whl", hash = "sha256:f7f3071b5faa605b0ea51ec4bb3ea7257277446b053f4fd3ad02b1dcb4e64353"}, + {file = "regex-2025.7.34-cp39-cp39-win_arm64.whl", hash = "sha256:716a47515ba1d03f8e8a61c5013041c8c90f2e21f055203498105d7571b44531"}, + {file = "regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a"}, ] [[package]] @@ -4601,7 +1862,6 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -4617,97 +1877,26 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -description = "OAuthlib authentication support for Requests." -optional = false -python-versions = ">=3.4" -groups = ["dev"] -files = [ - {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, - {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, -] - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - [[package]] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" -[[package]] -name = "respx" -version = "0.21.1" -description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "respx-0.21.1-py2.py3-none-any.whl", hash = "sha256:05f45de23f0c785862a2c92a3e173916e8ca88e4caad715dd5f68584d6053c20"}, - {file = "respx-0.21.1.tar.gz", hash = "sha256:0bd7fe21bfaa52106caa1223ce61224cf30786985f17c63c5d71eff0307ee8af"}, -] - -[package.dependencies] -httpx = ">=0.21.0" - -[[package]] -name = "rich" -version = "13.7.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -groups = ["dev"] -files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -groups = ["dev"] -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - [[package]] name = "ruff" version = "0.5.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, @@ -4729,222 +1918,94 @@ files = [ {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, ] -[[package]] -name = "s3transfer" -version = "0.10.1" -description = "An Amazon S3 Transfer Manager" -optional = false -python-versions = ">= 3.8" -groups = ["dev"] -files = [ - {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, - {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, -] - -[package.dependencies] -botocore = ">=1.33.2,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] - -[[package]] -name = "setuptools" -version = "78.1.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, - {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - -[[package]] -name = "shapely" -version = "2.0.4" -description = "Manipulation and analysis of geometric objects" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f"}, - {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17"}, - {file = "shapely-2.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd"}, - {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6"}, - {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58"}, - {file = "shapely-2.0.4-cp310-cp310-win32.whl", hash = "sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7"}, - {file = "shapely-2.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7"}, - {file = "shapely-2.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07"}, - {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47"}, - {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d"}, - {file = "shapely-2.0.4-cp311-cp311-win32.whl", hash = "sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879"}, - {file = "shapely-2.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65"}, - {file = "shapely-2.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6"}, - {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac"}, - {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30"}, - {file = "shapely-2.0.4-cp312-cp312-win32.whl", hash = "sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f"}, - {file = "shapely-2.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0"}, - {file = "shapely-2.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b"}, - {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf"}, - {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113"}, - {file = "shapely-2.0.4-cp37-cp37m-win32.whl", hash = "sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53"}, - {file = "shapely-2.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa"}, - {file = "shapely-2.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e"}, - {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679"}, - {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"}, - {file = "shapely-2.0.4-cp38-cp38-win32.whl", hash = "sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7"}, - {file = "shapely-2.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa"}, - {file = "shapely-2.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc"}, - {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242"}, - {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc"}, - {file = "shapely-2.0.4-cp39-cp39-win32.whl", hash = "sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c"}, - {file = "shapely-2.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398"}, - {file = "shapely-2.0.4.tar.gz", hash = "sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8"}, -] - -[package.dependencies] -numpy = ">=1.14,<3" - -[package.extras] -docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["dev"] -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - [[package]] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] -[[package]] -name = "soupsieve" -version = "2.5" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, -] - [[package]] name = "sqlalchemy" -version = "2.0.29" +version = "2.0.42" description = "Database Abstraction Library" -optional = false +optional = true python-versions = ">=3.7" -groups = ["main", "dev"] files = [ - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, - {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, - {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, -] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""} + {file = "SQLAlchemy-2.0.42-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ee065898359fdee83961aed5cf1fb4cfa913ba71b58b41e036001d90bebbf7a"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bc76d86216443daa2e27e6b04a9b96423f0b69b5d0c40c7f4b9a4cdf7d8d90"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89143290fb94c50a8dec73b06109ccd245efd8011d24fc0ddafe89dc55b36651"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:4efbdc9754c7145a954911bfeef815fb0843e8edab0e9cecfa3417a5cbd316af"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:88f8a8007a658dfd82c16a20bd9673ae6b33576c003b5166d42697d49e496e61"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-win32.whl", hash = "sha256:c5dd245e6502990ccf612d51f220a7b04cbea3f00f6030691ffe27def76ca79b"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-win_amd64.whl", hash = "sha256:5651eb19cacbeb2fe7431e4019312ed00a0b3fbd2d701423e0e2ceaadb5bcd9f"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:172b244753e034d91a826f80a9a70f4cbac690641207f2217f8404c261473efe"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be28f88abd74af8519a4542185ee80ca914933ca65cdfa99504d82af0e4210df"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b344859d282fde388047f1710860bb23f4098f705491e06b8ab52a48aafea9"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97978d223b11f1d161390a96f28c49a13ce48fdd2fed7683167c39bdb1b8aa09"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e35b9b000c59fcac2867ab3a79fc368a6caca8706741beab3b799d47005b3407"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc7347ad7a7b1c78b94177f2d57263113bb950e62c59b96ed839b131ea4234e1"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-win32.whl", hash = "sha256:739e58879b20a179156b63aa21f05ccacfd3e28e08e9c2b630ff55cd7177c4f1"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-win_amd64.whl", hash = "sha256:1aef304ada61b81f1955196f584b9e72b798ed525a7c0b46e09e98397393297b"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c34100c0b7ea31fbc113c124bcf93a53094f8951c7bf39c45f39d327bad6d1e7"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad59dbe4d1252448c19d171dfba14c74e7950b46dc49d015722a4a06bfdab2b0"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9187498c2149919753a7fd51766ea9c8eecdec7da47c1b955fa8090bc642eaa"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f092cf83ebcafba23a247f5e03f99f5436e3ef026d01c8213b5eca48ad6efa9"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc6afee7e66fdba4f5a68610b487c1f754fccdc53894a9567785932dbb6a265e"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:260ca1d2e5910f1f1ad3fe0113f8fab28657cee2542cb48c2f342ed90046e8ec"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-win32.whl", hash = "sha256:2eb539fd83185a85e5fcd6b19214e1c734ab0351d81505b0f987705ba0a1e231"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-win_amd64.whl", hash = "sha256:9193fa484bf00dcc1804aecbb4f528f1123c04bad6a08d7710c909750fa76aeb"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09637a0872689d3eb71c41e249c6f422e3e18bbd05b4cd258193cfc7a9a50da2"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3cb3ec67cc08bea54e06b569398ae21623534a7b1b23c258883a7c696ae10df"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87e6a5ef6f9d8daeb2ce5918bf5fddecc11cae6a7d7a671fcc4616c47635e01"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b718011a9d66c0d2f78e1997755cd965f3414563b31867475e9bc6efdc2281d"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16d9b544873fe6486dddbb859501a07d89f77c61d29060bb87d0faf7519b6a4d"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21bfdf57abf72fa89b97dd74d3187caa3172a78c125f2144764a73970810c4ee"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-win32.whl", hash = "sha256:78b46555b730a24901ceb4cb901c6b45c9407f8875209ed3c5d6bcd0390a6ed1"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-win_amd64.whl", hash = "sha256:4c94447a016f36c4da80072e6c6964713b0af3c8019e9c4daadf21f61b81ab53"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed5a6959b1668d97a32e3fd848b485f65ee3c05a759dee06d90e4545a3c77f1e"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2ddbaafe32f0dd12d64284b1c3189104b784c9f3dba8cc1ba7e642e2b14b906f"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37f4f42568b6c656ee177b3e111d354b5dda75eafe9fe63492535f91dfa35829"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb57923d852d38671a17abda9a65cc59e3e5eab51fb8307b09de46ed775bcbb8"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:437c2a8b0c780ff8168a470beb22cb4a25e1c63ea6a7aec87ffeb07aa4b76641"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:480f7df62f0b3ad6aa011eefa096049dc1770208bb71f234959ee2864206eefe"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-win32.whl", hash = "sha256:d119c80c614d62d32e236ae68e21dd28a2eaf070876b2f28a6075d5bae54ef3f"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-win_amd64.whl", hash = "sha256:be3a02f963c8d66e28bb4183bebab66dc4379701d92e660f461c65fecd6ff399"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78548fd65cd76d4c5a2e6b5f245d7734023ee4de33ee7bb298f1ac25a9935e0d"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf4bf5a174d8a679a713b7a896470ffc6baab78e80a79e7ec5668387ffeccc8b"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c7ff7ba08b375f8a8fa0511e595c9bdabb5494ec68f1cf69bb24e54c0d90f2"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b3c117f65d64e806ce5ce9ce578f06224dc36845e25ebd2554b3e86960e1aed"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:27e4a7b3a7a61ff919c2e7caafd612f8626114e6e5ebbe339de3b5b1df9bc27e"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b01e0dd39f96aefda5ab002d8402db4895db871eb0145836246ce0661635ce55"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-win32.whl", hash = "sha256:49362193b1f43aa158deebf438062d7b5495daa9177c6c5d0f02ceeb64b544ea"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-win_amd64.whl", hash = "sha256:636ec3dc83b2422a7ff548d0f8abf9c23742ca50e2a5cdc492a151eac7a0248b"}, + {file = "sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835"}, + {file = "sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f"}, +] + +[package.dependencies] +greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] +aioodbc = ["aioodbc", "greenlet (>=1)"] +aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (>=1)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] @@ -4954,7 +2015,7 @@ mysql-connector = ["mysql-connector-python"] oracle = ["cx_oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] postgresql-pg8000 = ["pg8000 (>=1.29.1)"] postgresql-psycopg = ["psycopg (>=3.0.7)"] postgresql-psycopg2binary = ["psycopg2-binary"] @@ -4963,115 +2024,59 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] -[[package]] -name = "starlette" -version = "0.47.2" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b"}, - {file = "starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8"}, -] - -[package.dependencies] -anyio = ">=3.6.2,<5" -typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} - -[package.extras] -full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] - -[[package]] -name = "sympy" -version = "1.12" -description = "Computer algebra system (CAS) in Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, - {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, -] - -[package.dependencies] -mpmath = ">=0.19" - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - [[package]] name = "tenacity" -version = "8.2.3" +version = "9.1.2" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] -markers = {main = "extra == \"langchain\""} [package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tiktoken" -version = "0.7.0" +version = "0.10.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, - {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, - {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79383a6e2c654c6040e5f8506f3750db9ddd71b550c724e673203b4f6b4b4590"}, - {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4511c52caacf3c4981d1ae2df85908bd31853f33d30b345c8b6830763f769c"}, - {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13c94efacdd3de9aff824a788353aa5749c0faee1fbe3816df365ea450b82311"}, - {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e58c7eb29d2ab35a7a8929cbeea60216a4ccdf42efa8974d8e176d50c9a3df5"}, - {file = "tiktoken-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:21a20c3bd1dd3e55b91c1331bf25f4af522c525e771691adbc9a69336fa7f702"}, - {file = "tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f"}, - {file = "tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f"}, - {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b"}, - {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992"}, - {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1"}, - {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89"}, - {file = "tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb"}, - {file = "tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908"}, - {file = "tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410"}, - {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704"}, - {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350"}, - {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4"}, - {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97"}, - {file = "tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f"}, - {file = "tiktoken-0.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2398fecd38c921bcd68418675a6d155fad5f5e14c2e92fcf5fe566fa5485a858"}, - {file = "tiktoken-0.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f5f6afb52fb8a7ea1c811e435e4188f2bef81b5e0f7a8635cc79b0eef0193d6"}, - {file = "tiktoken-0.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:861f9ee616766d736be4147abac500732b505bf7013cfaf019b85892637f235e"}, - {file = "tiktoken-0.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54031f95c6939f6b78122c0aa03a93273a96365103793a22e1793ee86da31685"}, - {file = "tiktoken-0.7.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fffdcb319b614cf14f04d02a52e26b1d1ae14a570f90e9b55461a72672f7b13d"}, - {file = "tiktoken-0.7.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c72baaeaefa03ff9ba9688624143c858d1f6b755bb85d456d59e529e17234769"}, - {file = "tiktoken-0.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:131b8aeb043a8f112aad9f46011dced25d62629091e51d9dc1adbf4a1cc6aa98"}, - {file = "tiktoken-0.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cabc6dc77460df44ec5b879e68692c63551ae4fae7460dd4ff17181df75f1db7"}, - {file = "tiktoken-0.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8d57f29171255f74c0aeacd0651e29aa47dff6f070cb9f35ebc14c82278f3b25"}, - {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ee92776fdbb3efa02a83f968c19d4997a55c8e9ce7be821ceee04a1d1ee149c"}, - {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e215292e99cb41fbc96988ef62ea63bb0ce1e15f2c147a61acc319f8b4cbe5bf"}, - {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a81bac94769cab437dd3ab0b8a4bc4e0f9cf6835bcaa88de71f39af1791727a"}, - {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d6d73ea93e91d5ca771256dfc9d1d29f5a554b83821a1dc0891987636e0ae226"}, - {file = "tiktoken-0.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:2bcb28ddf79ffa424f171dfeef9a4daff61a94c631ca6813f43967cb263b83b9"}, - {file = "tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6"}, + {file = "tiktoken-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1db7a65b196d757d18ef53193957d44549b88f373d4b87db532f04d18193b847"}, + {file = "tiktoken-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f55701461267d025597ebb2290d612fe9c5c5fbb625ebf7495c9f0f8e4c30f01"}, + {file = "tiktoken-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83693279af9e8deac0363cbf21dc3d666807f22dcc1091f51e69e6fe6433f71"}, + {file = "tiktoken-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f973bdeb68d645f73dcce60c7795cb5c6f8b7f3dcf92c40c39ad4aee398c075"}, + {file = "tiktoken-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d5cab2a8e8974d6c9744264e94886a58b9087a39737c53fd65dbe2fe8522e719"}, + {file = "tiktoken-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a49d2aedf911b68f23c9c84abce8eaf569cb02613f307cee4fb1bebd8c55ae9"}, + {file = "tiktoken-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:29f55c6b7c9a6a7ea953691c3962ee8fe4ee2f0ceb2a3fded3925acfc45e4b0a"}, + {file = "tiktoken-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f641d0735059b48252c4409d6546b6a62edeb4c4e48306499db11bbe403b872f"}, + {file = "tiktoken-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76e03676b36c2a5e5304b428cff48eb86a033af55212d41a6ac6faec25b2ad"}, + {file = "tiktoken-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ecbd4dc4db3b82ca3421f50d1207d750de1a2856e6ca0544294fe58d2853"}, + {file = "tiktoken-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d2e032cd44700a9ed511c50b9f302bb9e9e77e2ebf8e2d9b8ec12ce64ebf00c6"}, + {file = "tiktoken-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:eb520960fa1788caf033489e3b63bb675c0f321db40569b2d3ca24d7fab5ca72"}, + {file = "tiktoken-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c27021a605b2778af07f84a33fe7c74b5c960a8cfac5d2a7a1075ed592cbe4"}, + {file = "tiktoken-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:45d654f049b4f7ed617ad0c66462c15cc41e5db120f37f122d5b57ffa2aec062"}, + {file = "tiktoken-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34f73d93641b9cc73e19b18dbf9ce1baf086f1c4e18cfa36b952956843bca23c"}, + {file = "tiktoken-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fbfc5dda624c0f85c56bbf8f2ffb42964c798fc6fc1c321a452bf31d4ba21f5"}, + {file = "tiktoken-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:853671af10636b581d632fe7b64b755301c73bc1f2ce837a8cdd9a44ab51f846"}, + {file = "tiktoken-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:56c4e7544be63c78265c82857b9f2a4e2190ae29541fe3467708fc7aabcb1522"}, + {file = "tiktoken-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10e779827ddb0490a415d09d95f8e7a982fd8e14f88c4c2348358f722af3c415"}, + {file = "tiktoken-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:783892cdbec0d33f0f51373d8969e059ab1244af531a52338f702131a032a116"}, + {file = "tiktoken-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43f38d62b6a62a7c82be3770fcdc24cdf5bff3bdf718909d038b804ac774a025"}, + {file = "tiktoken-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72d0b30e08f1e856fa76ee423a3d3f0b1bc984cb6e86a21dbd7c5eee8f67f8f5"}, + {file = "tiktoken-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa840eba09a0c1db0b436baf6cfe1ab7906f33fb663cf96f772a818bad5856a6"}, + {file = "tiktoken-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:642b8d9f266004629ea2457aa29a9eed4f1a031fd804f8c489edbf32df7026c3"}, + {file = "tiktoken-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28fcb8555bb3e9cc6d625385b688b9781e7eb022e76826e721ad93198920034b"}, + {file = "tiktoken-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8e20f7da9086cb5b88eb53ef7ee2fd2db9f3b55c0bcddad51d1ad001a6fcf07"}, + {file = "tiktoken-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f795e7c451d560142c2d88d239ae9fcc11f0b4647376db625c9d4be8f16da45"}, + {file = "tiktoken-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8b3a5717679abb3e8a2097d9577e4c69b975c7fc0381d42598fa6f36f040fc3"}, + {file = "tiktoken-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b061e5035a561a18aefcd969079f1952b4caee3bb204ecfadb4bb41b81b8331b"}, + {file = "tiktoken-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a08250ef9b688c84a7f013fee01d587e73aaf41839bcd564105535276c55122b"}, + {file = "tiktoken-0.10.0.tar.gz", hash = "sha256:7cd88c11699b18081822e6ae1beee55e8f20ea361d73c507d33f5a89a1898f1c"}, ] [package.dependencies] @@ -5081,261 +2086,92 @@ requests = ">=2.26.0" [package.extras] blobfile = ["blobfile (>=2)"] -[[package]] -name = "tokenizers" -version = "0.19.1" -description = "" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "tokenizers-0.19.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:952078130b3d101e05ecfc7fc3640282d74ed26bcf691400f872563fca15ac97"}, - {file = "tokenizers-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82c8b8063de6c0468f08e82c4e198763e7b97aabfe573fd4cf7b33930ca4df77"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f03727225feaf340ceeb7e00604825addef622d551cbd46b7b775ac834c1e1c4"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:453e4422efdfc9c6b6bf2eae00d5e323f263fff62b29a8c9cd526c5003f3f642"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02e81bf089ebf0e7f4df34fa0207519f07e66d8491d963618252f2e0729e0b46"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b07c538ba956843833fee1190cf769c60dc62e1cf934ed50d77d5502194d63b1"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28cab1582e0eec38b1f38c1c1fb2e56bce5dc180acb1724574fc5f47da2a4fe"}, - {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b01afb7193d47439f091cd8f070a1ced347ad0f9144952a30a41836902fe09e"}, - {file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7fb297edec6c6841ab2e4e8f357209519188e4a59b557ea4fafcf4691d1b4c98"}, - {file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e8a3dd055e515df7054378dc9d6fa8c8c34e1f32777fb9a01fea81496b3f9d3"}, - {file = "tokenizers-0.19.1-cp310-none-win32.whl", hash = "sha256:7ff898780a155ea053f5d934925f3902be2ed1f4d916461e1a93019cc7250837"}, - {file = "tokenizers-0.19.1-cp310-none-win_amd64.whl", hash = "sha256:bea6f9947e9419c2fda21ae6c32871e3d398cba549b93f4a65a2d369662d9403"}, - {file = "tokenizers-0.19.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5c88d1481f1882c2e53e6bb06491e474e420d9ac7bdff172610c4f9ad3898059"}, - {file = "tokenizers-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddf672ed719b4ed82b51499100f5417d7d9f6fb05a65e232249268f35de5ed14"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dadc509cc8a9fe460bd274c0e16ac4184d0958117cf026e0ea8b32b438171594"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfedf31824ca4915b511b03441784ff640378191918264268e6923da48104acc"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac11016d0a04aa6487b1513a3a36e7bee7eec0e5d30057c9c0408067345c48d2"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76951121890fea8330d3a0df9a954b3f2a37e3ec20e5b0530e9a0044ca2e11fe"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b342d2ce8fc8d00f376af068e3274e2e8649562e3bc6ae4a67784ded6b99428d"}, - {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d16ff18907f4909dca9b076b9c2d899114dd6abceeb074eca0c93e2353f943aa"}, - {file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:706a37cc5332f85f26efbe2bdc9ef8a9b372b77e4645331a405073e4b3a8c1c6"}, - {file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16baac68651701364b0289979ecec728546133e8e8fe38f66fe48ad07996b88b"}, - {file = "tokenizers-0.19.1-cp311-none-win32.whl", hash = "sha256:9ed240c56b4403e22b9584ee37d87b8bfa14865134e3e1c3fb4b2c42fafd3256"}, - {file = "tokenizers-0.19.1-cp311-none-win_amd64.whl", hash = "sha256:ad57d59341710b94a7d9dbea13f5c1e7d76fd8d9bcd944a7a6ab0b0da6e0cc66"}, - {file = "tokenizers-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:621d670e1b1c281a1c9698ed89451395d318802ff88d1fc1accff0867a06f153"}, - {file = "tokenizers-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d924204a3dbe50b75630bd16f821ebda6a5f729928df30f582fb5aade90c818a"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f3fefdc0446b1a1e6d81cd4c07088ac015665d2e812f6dbba4a06267d1a2c95"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9620b78e0b2d52ef07b0d428323fb34e8ea1219c5eac98c2596311f20f1f9266"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04ce49e82d100594715ac1b2ce87d1a36e61891a91de774755f743babcd0dd52"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5c2ff13d157afe413bf7e25789879dd463e5a4abfb529a2d8f8473d8042e28f"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3174c76efd9d08f836bfccaca7cfec3f4d1c0a4cf3acbc7236ad577cc423c840"}, - {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9d5b6c0e7a1e979bec10ff960fae925e947aab95619a6fdb4c1d8ff3708ce3"}, - {file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a179856d1caee06577220ebcfa332af046d576fb73454b8f4d4b0ba8324423ea"}, - {file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:952b80dac1a6492170f8c2429bd11fcaa14377e097d12a1dbe0ef2fb2241e16c"}, - {file = "tokenizers-0.19.1-cp312-none-win32.whl", hash = "sha256:01d62812454c188306755c94755465505836fd616f75067abcae529c35edeb57"}, - {file = "tokenizers-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:b70bfbe3a82d3e3fb2a5e9b22a39f8d1740c96c68b6ace0086b39074f08ab89a"}, - {file = "tokenizers-0.19.1-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:bb9dfe7dae85bc6119d705a76dc068c062b8b575abe3595e3c6276480e67e3f1"}, - {file = "tokenizers-0.19.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:1f0360cbea28ea99944ac089c00de7b2e3e1c58f479fb8613b6d8d511ce98267"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:71e3ec71f0e78780851fef28c2a9babe20270404c921b756d7c532d280349214"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b82931fa619dbad979c0ee8e54dd5278acc418209cc897e42fac041f5366d626"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8ff5b90eabdcdaa19af697885f70fe0b714ce16709cf43d4952f1f85299e73a"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e742d76ad84acbdb1a8e4694f915fe59ff6edc381c97d6dfdd054954e3478ad4"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8c5d59d7b59885eab559d5bc082b2985555a54cda04dda4c65528d90ad252ad"}, - {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b2da5c32ed869bebd990c9420df49813709e953674c0722ff471a116d97b22d"}, - {file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:638e43936cc8b2cbb9f9d8dde0fe5e7e30766a3318d2342999ae27f68fdc9bd6"}, - {file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:78e769eb3b2c79687d9cb0f89ef77223e8e279b75c0a968e637ca7043a84463f"}, - {file = "tokenizers-0.19.1-cp37-none-win32.whl", hash = "sha256:72791f9bb1ca78e3ae525d4782e85272c63faaef9940d92142aa3eb79f3407a3"}, - {file = "tokenizers-0.19.1-cp37-none-win_amd64.whl", hash = "sha256:f3bbb7a0c5fcb692950b041ae11067ac54826204318922da754f908d95619fbc"}, - {file = "tokenizers-0.19.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:07f9295349bbbcedae8cefdbcfa7f686aa420be8aca5d4f7d1ae6016c128c0c5"}, - {file = "tokenizers-0.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10a707cc6c4b6b183ec5dbfc5c34f3064e18cf62b4a938cb41699e33a99e03c1"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6309271f57b397aa0aff0cbbe632ca9d70430839ca3178bf0f06f825924eca22"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ad23d37d68cf00d54af184586d79b84075ada495e7c5c0f601f051b162112dc"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:427c4f0f3df9109314d4f75b8d1f65d9477033e67ffaec4bca53293d3aca286d"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e83a31c9cf181a0a3ef0abad2b5f6b43399faf5da7e696196ddd110d332519ee"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c27b99889bd58b7e301468c0838c5ed75e60c66df0d4db80c08f43462f82e0d3"}, - {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bac0b0eb952412b0b196ca7a40e7dce4ed6f6926489313414010f2e6b9ec2adf"}, - {file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8a6298bde623725ca31c9035a04bf2ef63208d266acd2bed8c2cb7d2b7d53ce6"}, - {file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:08a44864e42fa6d7d76d7be4bec62c9982f6f6248b4aa42f7302aa01e0abfd26"}, - {file = "tokenizers-0.19.1-cp38-none-win32.whl", hash = "sha256:1de5bc8652252d9357a666e609cb1453d4f8e160eb1fb2830ee369dd658e8975"}, - {file = "tokenizers-0.19.1-cp38-none-win_amd64.whl", hash = "sha256:0bcce02bf1ad9882345b34d5bd25ed4949a480cf0e656bbd468f4d8986f7a3f1"}, - {file = "tokenizers-0.19.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0b9394bd204842a2a1fd37fe29935353742be4a3460b6ccbaefa93f58a8df43d"}, - {file = "tokenizers-0.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4692ab92f91b87769d950ca14dbb61f8a9ef36a62f94bad6c82cc84a51f76f6a"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6258c2ef6f06259f70a682491c78561d492e885adeaf9f64f5389f78aa49a051"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85cf76561fbd01e0d9ea2d1cbe711a65400092bc52b5242b16cfd22e51f0c58"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:670b802d4d82bbbb832ddb0d41df7015b3e549714c0e77f9bed3e74d42400fbe"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85aa3ab4b03d5e99fdd31660872249df5e855334b6c333e0bc13032ff4469c4a"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbf001afbbed111a79ca47d75941e9e5361297a87d186cbfc11ed45e30b5daba"}, - {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c89aa46c269e4e70c4d4f9d6bc644fcc39bb409cb2a81227923404dd6f5227"}, - {file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:39c1ec76ea1027438fafe16ecb0fb84795e62e9d643444c1090179e63808c69d"}, - {file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c2a0d47a89b48d7daa241e004e71fb5a50533718897a4cd6235cb846d511a478"}, - {file = "tokenizers-0.19.1-cp39-none-win32.whl", hash = "sha256:61b7fe8886f2e104d4caf9218b157b106207e0f2a4905c9c7ac98890688aabeb"}, - {file = "tokenizers-0.19.1-cp39-none-win_amd64.whl", hash = "sha256:f97660f6c43efd3e0bfd3f2e3e5615bf215680bad6ee3d469df6454b8c6e8256"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b11853f17b54c2fe47742c56d8a33bf49ce31caf531e87ac0d7d13d327c9334"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d26194ef6c13302f446d39972aaa36a1dda6450bc8949f5eb4c27f51191375bd"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e8d1ed93beda54bbd6131a2cb363a576eac746d5c26ba5b7556bc6f964425594"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca407133536f19bdec44b3da117ef0d12e43f6d4b56ac4c765f37eca501c7bda"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce05fde79d2bc2e46ac08aacbc142bead21614d937aac950be88dc79f9db9022"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:35583cd46d16f07c054efd18b5d46af4a2f070a2dd0a47914e66f3ff5efb2b1e"}, - {file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:43350270bfc16b06ad3f6f07eab21f089adb835544417afda0f83256a8bf8b75"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b4399b59d1af5645bcee2072a463318114c39b8547437a7c2d6a186a1b5a0e2d"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6852c5b2a853b8b0ddc5993cd4f33bfffdca4fcc5d52f89dd4b8eada99379285"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd266ae85c3d39df2f7e7d0e07f6c41a55e9a3123bb11f854412952deacd828"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecb2651956eea2aa0a2d099434134b1b68f1c31f9a5084d6d53f08ed43d45ff2"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b279ab506ec4445166ac476fb4d3cc383accde1ea152998509a94d82547c8e2a"}, - {file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:89183e55fb86e61d848ff83753f64cded119f5d6e1f553d14ffee3700d0a4a49"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2edbc75744235eea94d595a8b70fe279dd42f3296f76d5a86dde1d46e35f574"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0e64bfde9a723274e9a71630c3e9494ed7b4c0f76a1faacf7fe294cd26f7ae7c"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0b5ca92bfa717759c052e345770792d02d1f43b06f9e790ca0a1db62838816f3"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f8a20266e695ec9d7a946a019c1d5ca4eddb6613d4f466888eee04f16eedb85"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63c38f45d8f2a2ec0f3a20073cccb335b9f99f73b3c69483cd52ebc75369d8a1"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dd26e3afe8a7b61422df3176e06664503d3f5973b94f45d5c45987e1cb711876"}, - {file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:eddd5783a4a6309ce23432353cdb36220e25cbb779bfa9122320666508b44b88"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:56ae39d4036b753994476a1b935584071093b55c7a72e3b8288e68c313ca26e7"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f9939ca7e58c2758c01b40324a59c034ce0cebad18e0d4563a9b1beab3018243"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c330c0eb815d212893c67a032e9dc1b38a803eccb32f3e8172c19cc69fbb439"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec11802450a2487cdf0e634b750a04cbdc1c4d066b97d94ce7dd2cb51ebb325b"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b718f316b596f36e1dae097a7d5b91fc5b85e90bf08b01ff139bd8953b25af"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ed69af290c2b65169f0ba9034d1dc39a5db9459b32f1dd8b5f3f32a3fcf06eab"}, - {file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f8a9c828277133af13f3859d1b6bf1c3cb6e9e1637df0e45312e6b7c2e622b1f"}, - {file = "tokenizers-0.19.1.tar.gz", hash = "sha256:ee59e6680ed0fdbe6b724cf38bd70400a0c1dd623b07ac729087270caeac88e3"}, -] - -[package.dependencies] -huggingface-hub = ">=0.16.4,<1.0" - -[package.extras] -dev = ["tokenizers[testing]"] -docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] -testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] - [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version <= \"3.10\"" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "tqdm" -version = "4.66.3" +version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ - {file = "tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53"}, - {file = "tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5"}, + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "typer" -version = "0.12.3" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, - {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "types-requests" -version = "2.31.0.6" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, - {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, -] - -[package.dependencies] -types-urllib3 = "*" - -[[package]] -name = "types-urllib3" -version = "1.26.25.14" -description = "Typing stubs for urllib3" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, - {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, -] - [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] -name = "typing-inspect" -version = "0.9.0" -description = "Runtime inspection utilities for typing module." +name = "typing-inspection" +version = "0.4.1" +description = "Runtime typing introspection tools" optional = false -python-versions = "*" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, - {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, ] [package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["dev"] -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "urllib3" -version = "1.26.20" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main", "dev"] -markers = "python_version < \"3.10\"" -files = [ - {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, - {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +typing-extensions = ">=4.12.0" [[package]] name = "urllib3" @@ -5343,102 +2179,26 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] -markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "uvicorn" -version = "0.29.0" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, - {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} -h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} - -[package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0) ; python_version >= \"3.12\"", "aiohttp (>=3.8.1) ; python_version < \"3.12\"", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.33.1" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, + {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, ] [package.dependencies] @@ -5448,205 +2208,17 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - -[[package]] -name = "watchfiles" -version = "0.21.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, - {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"}, - {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"}, - {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, - {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, - {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, - {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"}, - {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"}, - {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"}, - {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"}, - {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"}, - {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"}, - {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"}, - {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"}, - {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "websocket-client" -version = "1.8.0" -description = "WebSocket client for Python with low level API options" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, - {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, -] - -[package.extras] -docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "websockets" -version = "12.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, -] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "werkzeug" -version = "3.0.6" +version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, - {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, ] [package.dependencies] @@ -5657,204 +2229,228 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wrapt" -version = "1.16.0" +version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "yarl" -version = "1.18.3" -description = "Yet another URL library" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] +python-versions = ">=3.8" files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, - {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, - {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] -markers = {main = "extra == \"langchain\""} - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.0" [[package]] name = "zipp" -version = "3.19.1" +version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, - {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[[package]] +name = "zstandard" +version = "0.23.0" +description = "Zstandard bindings for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, + {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, + {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, + {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, + {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, + {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, + {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, + {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, + {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, + {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, + {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"}, + {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"}, + {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"}, + {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"}, + {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"}, + {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"}, + {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"}, + {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, + {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, +] + +[package.dependencies] +cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} + +[package.extras] +cffi = ["cffi (>=1.11)"] [extras] langchain = ["langchain"] -llama-index = [] openai = ["openai"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "ac31e0db93cfcbf0adbb201a20b1547d1af5347437062d55142d3b24838f711e" +content-hash = "746d378f1bbcf8c86346a0ac7a4ebb400462d38e43eee5d7edf51dd78a8307a1" diff --git a/pyproject.toml b/pyproject.toml index e45afb74c..4f9db2c54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,37 +23,14 @@ opentelemetry-exporter-otlp = "^1.33.1" [tool.poetry.group.dev.dependencies] pytest = ">=7.4,<9.0" -chromadb = ">=0.4.2,<0.6.0" -tiktoken = "0.7.0" pytest-timeout = "^2.1.0" pytest-xdist = "^3.3.1" -respx = ">=0.20.2,<0.22.0" -google-search-results = "^2.4.2" -huggingface_hub = ">=0.16.4,<0.25.0" pre-commit = "^3.2.2" -anthropic = ">=0.17.0,<1" -bs4 = ">=0.0.1,<0.0.3" -lark = "^1.1.7" pytest-asyncio = ">=0.21.1,<0.24.0" pytest-httpserver = "^1.0.8" -boto3 = "^1.28.59" ruff = ">=0.1.8,<0.6.0" mypy = "^1.0.0" -langchain-mistralai = ">=0.0.1,<0.3" -google-cloud-aiplatform = "^1.38.1" -cohere = ">=4.46,<6.0" -langchain-google-vertexai = ">=1.0.0,<3.0.0" langchain-openai = ">=0.0.5,<0.3" -dashscope = "^1.14.1" -pymongo = "^4.6.1" -llama-index-llms-anthropic = ">=0.1.1,<0.6" -bson = "^0.5.10" -langchain-anthropic = ">=0.1.4,<0.4" -langchain-groq = ">=0.1.3,<0.3" -langchain-aws = ">=0.1.3,<0.3" -langchain-ollama = "^0.2.0" -langchain-cohere = "^0.3.3" -langchain-community = ">=0.2.14,<0.4" langgraph = "^0.2.62" [tool.poetry.group.docs.dependencies] @@ -62,7 +39,6 @@ pdoc = "^14.4.0" [tool.poetry.extras] openai = ["openai"] langchain = ["langchain"] -llama-index = ["llama-index"] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/test_extract_model.py b/tests/test_extract_model.py deleted file mode 100644 index 5db2961f6..000000000 --- a/tests/test_extract_model.py +++ /dev/null @@ -1,158 +0,0 @@ -from typing import Any -from unittest.mock import MagicMock - -import pytest -from langchain.schema.messages import HumanMessage -from langchain_anthropic import Anthropic, ChatAnthropic -from langchain_aws import BedrockLLM, ChatBedrock -from langchain_community.chat_models import ( - ChatCohere, - ChatTongyi, -) -from langchain_community.chat_models.fake import FakeMessagesListChatModel - -# from langchain_huggingface.llms import HuggingFacePipeline -from langchain_community.llms.textgen import TextGen -from langchain_core.load.dump import default -from langchain_google_vertexai import ChatVertexAI -from langchain_groq import ChatGroq -from langchain_mistralai.chat_models import ChatMistralAI -from langchain_ollama import ChatOllama, OllamaLLM -from langchain_openai import ( - AzureChatOpenAI, - ChatOpenAI, - OpenAI, -) - -from langfuse.langchain import CallbackHandler -from langfuse.langchain.utils import _extract_model_name -from tests.utils import get_api - - -@pytest.mark.parametrize( - "expected_model,model", - [ - ( - "mixtral-8x7b-32768", - ChatGroq( - temperature=0, model_name="mixtral-8x7b-32768", groq_api_key="something" - ), - ), - ("llama3", OllamaLLM(model="llama3")), - ("llama3", ChatOllama(model="llama3")), - ( - None, - FakeMessagesListChatModel(responses=[HumanMessage("Hello, how are you?")]), - ), - ( - "mistralai", - ChatMistralAI(mistral_api_key="mistral_api_key", model="mistralai"), - ), - ( - "text-gen", - TextGen(model_url="some-url"), - ), # local deployments, does not have a model name - ("claude-2", ChatAnthropic(model_name="claude-2")), - ( - "claude-3-sonnet-20240229", - ChatAnthropic(model="claude-3-sonnet-20240229"), - ), - ("claude-2", Anthropic()), - ("claude-2", Anthropic()), - ("command", ChatCohere(model="command", cohere_api_key="command")), - (None, ChatTongyi(dashscope_api_key="dash")), - ( - "amazon.titan-tg1-large", - BedrockLLM( - model="amazon.titan-tg1-large", - region="us-east-1", - client=MagicMock(), - ), - ), - ( - "anthropic.claude-3-sonnet-20240229-v1:0", - ChatBedrock( - model_id="anthropic.claude-3-sonnet-20240229-v1:0", - region_name="us-east-1", - client=MagicMock(), - ), - ), - ( - "claude-1", - BedrockLLM( - model="claude-1", - region="us-east-1", - client=MagicMock(), - ), - ), - ], -) -def test_models(expected_model: str, model: Any): - serialized = default(model) - model_name = _extract_model_name(serialized) - assert model_name == expected_model - - -# all models here need to be tested here because we take the model from the kwargs / invocation_params or we need to make an actual call for setup -@pytest.mark.skip("Flaky") -@pytest.mark.parametrize( - "expected_model,model", - [ - ("gpt-3.5-turbo-0125", ChatOpenAI()), - ("gpt-3.5-turbo-instruct", OpenAI()), - ( - "gpt-3.5-turbo", - AzureChatOpenAI( - openai_api_version="2023-05-15", - model="gpt-3.5-turbo", - azure_deployment="your-deployment-name", - azure_endpoint="https://your-endpoint-name.azurewebsites.net", - ), - ), - # ( - # "gpt2", - # HuggingFacePipeline( - # model_id="gpt2", - # model_kwargs={ - # "max_new_tokens": 512, - # "top_k": 30, - # "temperature": 0.1, - # "repetition_penalty": 1.03, - # }, - # ), - # ), - ( - "qwen-72b-chat", - ChatTongyi(model="qwen-72b-chat", dashscope_api_key="dashscope"), - ), - ( - "gemini", - ChatVertexAI( - model_name="gemini", credentials=MagicMock(), project="some-project" - ), - ), - ], -) -def test_entire_llm_call(expected_model, model): - callback = CallbackHandler() - - with callback.client.start_as_current_span(name="parent") as span: - trace_id = span.trace_id - - try: - # LLM calls are failing, because of missing API keys etc. - # However, we are still able to extract the model names beforehand. - model.invoke("Hello, how are you?", config={"callbacks": [callback]}) - except Exception as e: - print(e) - pass - - callback.client.flush() - api = get_api() - - trace = api.trace.get(trace_id) - - assert len(trace.observations) == 2 - - generation = list(filter(lambda o: o.type == "GENERATION", trace.observations))[0] - assert generation.model == expected_model diff --git a/tests/test_json.py b/tests/test_json.py index bf0e38c65..afcacfc76 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -7,7 +7,6 @@ from unittest.mock import patch import pytest -from bson import ObjectId from langchain.schema.messages import HumanMessage from pydantic import BaseModel @@ -129,11 +128,3 @@ def test_observation_level(): result = json.dumps(ObservationLevel.ERROR, cls=EventSerializer) assert result == '"ERROR"' - - -def test_mongo_cursor(): - test_id = ObjectId("5f3e3e3e3e3e3e3e3e3e3e3e") - - result = json.dumps(test_id, cls=EventSerializer) - - assert isinstance(result, str) diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 5bf764999..71b0cb5f1 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -7,20 +7,14 @@ import pytest from langchain.chains import ( - ConversationalRetrievalChain, ConversationChain, LLMChain, - RetrievalQA, SimpleSequentialChain, ) from langchain.chains.openai_functions import create_openai_fn_chain from langchain.memory import ConversationBufferMemory from langchain.prompts import ChatPromptTemplate, PromptTemplate from langchain.schema import HumanMessage, SystemMessage -from langchain.text_splitter import CharacterTextSplitter -from langchain_community.document_loaders import TextLoader -from langchain_community.embeddings import OpenAIEmbeddings -from langchain_community.vectorstores import Chroma from langchain_core.callbacks.manager import CallbackManagerForLLMRun from langchain_core.language_models.llms import LLM from langchain_core.output_parsers import StrOutputParser @@ -35,7 +29,6 @@ from langfuse._client.client import Langfuse from langfuse.langchain import CallbackHandler from langfuse.langchain.CallbackHandler import LANGSMITH_TAG_HIDDEN -from tests.api_wrapper import LangfuseAPI from tests.utils import create_uuid, encode_file_to_base64, get_api @@ -226,89 +219,6 @@ def test_basic_chat_openai(): assert generation.output is not None -def test_callback_retriever(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="retriever_test") as span: - trace_id = span.trace_id - handler = CallbackHandler() - - loader = TextLoader("./static/state_of_the_union.txt", encoding="utf8") - llm = OpenAI() - - documents = loader.load() - text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) - texts = text_splitter.split_documents(documents) - - embeddings = OpenAIEmbeddings() - docsearch = Chroma.from_documents(texts, embeddings) - - query = "What did the president say about Ketanji Brown Jackson" - - chain = RetrievalQA.from_chain_type( - llm, - retriever=docsearch.as_retriever(), - ) - - chain.run(query, callbacks=[handler]) - - langfuse.flush() - - trace = get_api().trace.get(trace_id) - - assert len(trace.observations) == 6 - for observation in trace.observations: - if observation.type == "GENERATION": - assert observation.usage_details["input"] > 0 - assert observation.usage_details["output"] > 0 - assert observation.usage_details["total"] > 0 - assert observation.input is not None - assert observation.input != "" - assert observation.output is not None - assert observation.output != "" - - -def test_callback_retriever_with_sources(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="retriever_with_sources_test") as span: - trace_id = span.trace_id - handler = CallbackHandler() - - loader = TextLoader("./static/state_of_the_union.txt", encoding="utf8") - llm = OpenAI() - - documents = loader.load() - text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) - texts = text_splitter.split_documents(documents) - - embeddings = OpenAIEmbeddings() - docsearch = Chroma.from_documents(texts, embeddings) - - query = "What did the president say about Ketanji Brown Jackson" - - chain = RetrievalQA.from_chain_type( - llm, retriever=docsearch.as_retriever(), return_source_documents=True - ) - - chain(query, callbacks=[handler]) - - langfuse.flush() - - trace = get_api().trace.get(trace_id) - - assert len(trace.observations) == 6 - for observation in trace.observations: - if observation.type == "GENERATION": - assert observation.usage_details["input"] > 0 - assert observation.usage_details["output"] > 0 - assert observation.usage_details["total"] > 0 - assert observation.input is not None - assert observation.input != "" - assert observation.output is not None - assert observation.output != "" - - def test_callback_retriever_conversational_with_memory(): langfuse = Langfuse() @@ -347,54 +257,6 @@ def test_callback_retriever_conversational_with_memory(): assert generation.usage_details["output"] is not None -def test_callback_retriever_conversational(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="retriever_conversational_test") as span: - trace_id = span.trace_id - api_wrapper = LangfuseAPI() - handler = CallbackHandler() - - loader = TextLoader("./static/state_of_the_union.txt", encoding="utf8") - - documents = loader.load() - text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) - texts = text_splitter.split_documents(documents) - - embeddings = OpenAIEmbeddings(openai_api_key=os.environ.get("OPENAI_API_KEY")) - docsearch = Chroma.from_documents(texts, embeddings) - - query = "What did the president say about Ketanji Brown Jackson" - - chain = ConversationalRetrievalChain.from_llm( - ChatOpenAI( - openai_api_key=os.environ.get("OPENAI_API_KEY"), - temperature=0.5, - model="gpt-3.5-turbo-16k", - ), - docsearch.as_retriever(search_kwargs={"k": 6}), - return_source_documents=True, - ) - - chain({"question": query, "chat_history": []}, callbacks=[handler]) - - handler.client.flush() - - trace = api_wrapper.get_trace(trace_id) - - # Add 1 to account for the wrapping span - assert len(trace["observations"]) == 6 - for observation in trace["observations"]: - if observation["type"] == "GENERATION": - assert observation["promptTokens"] > 0 - assert observation["completionTokens"] > 0 - assert observation["totalTokens"] > 0 - assert observation["input"] is not None - assert observation["input"] != "" - assert observation["output"] is not None - assert observation["output"] != "" - - def test_callback_simple_openai(): langfuse = Langfuse() diff --git a/tests/test_serializer.py b/tests/test_serializer.py index e9f1277bd..4faf7019b 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -174,38 +174,3 @@ def __init__(self): obj = SlotClass() serializer = EventSerializer() assert json.loads(serializer.encode(obj)) == {"field": "value"} - - -def test_numpy_float32(): - import numpy as np - - data = np.float32(1.0) - serializer = EventSerializer() - - assert serializer.encode(data) == "1.0" - - -def test_numpy_arrays(): - import numpy as np - - serializer = EventSerializer() - - # Test 1D array - arr_1d = np.array([1, 2, 3]) - assert json.loads(serializer.encode(arr_1d)) == [1, 2, 3] - - # Test 2D array - arr_2d = np.array([[1, 2], [3, 4]]) - assert json.loads(serializer.encode(arr_2d)) == [[1, 2], [3, 4]] - - # Test float array - arr_float = np.array([1.1, 2.2, 3.3]) - assert json.loads(serializer.encode(arr_float)) == [1.1, 2.2, 3.3] - - # Test empty array - arr_empty = np.array([]) - assert json.loads(serializer.encode(arr_empty)) == [] - - # Test mixed types that numpy can handle - arr_mixed = np.array([1, 2.5, 3]) - assert json.loads(serializer.encode(arr_mixed)) == [1.0, 2.5, 3.0] diff --git a/tests/utils.py b/tests/utils.py index 6b6849a6a..774c0c5c3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -9,15 +9,6 @@ except ImportError: import pydantic # type: ignore -from llama_index.core import ( - Settings, - SimpleDirectoryReader, - StorageContext, - VectorStoreIndex, - load_index_from_storage, -) -from llama_index.core.callbacks import CallbackManager - from langfuse.api.client import FernLangfuse @@ -91,26 +82,6 @@ def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return super().dict(**kwargs_with_defaults) -def get_llama_index_index(callback, force_rebuild: bool = False): - if callback: - Settings.callback_manager = CallbackManager([callback]) - PERSIST_DIR = "tests/mocks/llama-index-storage" - - if not os.path.exists(PERSIST_DIR) or force_rebuild: - print("Building RAG index...") - documents = SimpleDirectoryReader( - "static", ["static/state_of_the_union_short.txt"] - ).load_data() - index = VectorStoreIndex.from_documents(documents) - index.storage_context.persist(persist_dir=PERSIST_DIR) - else: - print("Using pre-built index from storage...") - storage_context = StorageContext.from_defaults(persist_dir=PERSIST_DIR) - index = load_index_from_storage(storage_context) - - return index - - def encode_file_to_base64(image_path) -> str: with open(image_path, "rb") as file: return base64.b64encode(file.read()).decode("utf-8") From ee8f0929ce9385a4b69bd862f45b4cde74b7c86a Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:51:41 +0200 Subject: [PATCH 008/296] fix(client): add warning message if OTEL_SDK_DISABLED (#1291) --- langfuse/_client/client.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index d205e5d51..6aba529fc 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -221,6 +221,11 @@ def __init__( self._otel_tracer = otel_trace_api.NoOpTracer() return + if os.environ.get("OTEL_SDK_DISABLED", "false").lower() == "true": + langfuse_logger.warning( + "OTEL_SDK_DISABLED is set. Langfuse tracing will be disabled and no traces will appear in the UI." + ) + # Initialize api and tracer if requirements are met self._resources = LangfuseResourceManager( public_key=public_key, From 75ae6b1604a729bac78ea150553bab1e2fa57835 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 08:59:52 +0000 Subject: [PATCH 009/296] chore(deps): bump openai from 1.99.5 to 1.99.6 (#1287) Bumps [openai](https://github.com/openai/openai-python) from 1.99.5 to 1.99.6. - [Release notes](https://github.com/openai/openai-python/releases) - [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/openai/openai-python/compare/v1.99.5...v1.99.6) --- updated-dependencies: - dependency-name: openai dependency-version: 1.99.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 135 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 122 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 644f9c5de..007b7fdbd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "4.10.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, @@ -37,6 +39,8 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"langchain\" and python_version < \"3.11\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -48,6 +52,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -59,6 +64,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -70,6 +76,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -139,6 +146,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "extra == \"langchain\" and platform_python_implementation == \"PyPy\"", dev = "platform_python_implementation == \"PyPy\""} [package.dependencies] pycparser = "*" @@ -149,6 +157,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -160,6 +169,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -261,10 +271,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "extra == \"openai\" and platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -272,6 +284,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -283,10 +296,12 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "exceptiongroup" @@ -294,6 +309,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -311,6 +328,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -325,6 +343,7 @@ version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -333,7 +352,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "googleapis-common-protos" @@ -341,6 +360,7 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -358,6 +378,8 @@ version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"langchain\"" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -425,6 +447,7 @@ version = "1.74.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907"}, {file = "grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb"}, @@ -488,6 +511,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -499,6 +523,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -520,6 +545,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -532,7 +558,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -544,6 +570,7 @@ version = "2.6.12" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, @@ -558,6 +585,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -572,6 +600,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -581,12 +610,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -595,6 +624,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -606,6 +636,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -623,6 +654,7 @@ version = "0.10.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, @@ -702,6 +734,7 @@ files = [ {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "jsonpatch" @@ -709,10 +742,12 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -723,10 +758,12 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "langchain" @@ -734,6 +771,8 @@ version = "0.3.27" description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.9" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, @@ -774,10 +813,12 @@ version = "0.3.74" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.74-py3-none-any.whl", hash = "sha256:088338b5bc2f6a66892f9afc777992c24ee3188f41cbc603d09181e34a228ce7"}, {file = "langchain_core-0.3.74.tar.gz", hash = "sha256:ff604441aeade942fbcc0a3860a592daba7671345230c2078ba2eb5f82b6ba76"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" @@ -794,6 +835,7 @@ version = "0.2.14" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8"}, {file = "langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978"}, @@ -810,6 +852,8 @@ version = "0.3.9" description = "LangChain text splitting utilities" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, @@ -824,6 +868,7 @@ version = "0.2.76" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = "<4.0,>=3.9.0" +groups = ["dev"] files = [ {file = "langgraph-0.2.76-py3-none-any.whl", hash = "sha256:076b8b5d2fc5a9761c46a7618430cfa5c978a8012257c43cbc127b27e0fd7872"}, {file = "langgraph-0.2.76.tar.gz", hash = "sha256:688f8dcd9b6797ba78384599e0de944773000c75156ad1e186490e99e89fa5c0"}, @@ -840,6 +885,7 @@ version = "2.1.1" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, @@ -855,6 +901,7 @@ version = "0.1.74" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_sdk-0.1.74-py3-none-any.whl", hash = "sha256:3a265c3757fe0048adad4391d10486db63ef7aa5a2cbd22da22d4503554cb890"}, {file = "langgraph_sdk-0.1.74.tar.gz", hash = "sha256:7450e0db5b226cc2e5328ca22c5968725873630ef47c4206a30707cb25dc3ad6"}, @@ -870,10 +917,12 @@ version = "0.4.13" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "langsmith-0.4.13-py3-none-any.whl", hash = "sha256:dab7b16ee16986995007bf5a777f45c18f8bf7453f67ae2ebcb46ce43c214297"}, {file = "langsmith-0.4.13.tar.gz", hash = "sha256:1ae7dbb5d8150647406f49885a2dd16ab12bd990254b5dc23718838b3d086fde"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -897,6 +946,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -967,6 +1017,7 @@ version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, @@ -1027,6 +1078,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1038,6 +1090,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1045,14 +1098,16 @@ files = [ [[package]] name = "openai" -version = "1.99.5" +version = "1.99.9" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "openai-1.99.5-py3-none-any.whl", hash = "sha256:4e870f9501b7c36132e2be13313ce3c4d6915a837e7a299c483aab6a6d4412e9"}, - {file = "openai-1.99.5.tar.gz", hash = "sha256:aa97ac3326cac7949c5e4ac0274c454c1d19c939760107ae0d3948fc26a924ca"}, + {file = "openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a"}, + {file = "openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -1076,6 +1131,7 @@ version = "1.36.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, @@ -1091,6 +1147,7 @@ version = "1.36.0" description = "OpenTelemetry Collector Exporters" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp-1.36.0-py3-none-any.whl", hash = "sha256:de93b7c45bcc78296998775d52add7c63729e83ef2cd6560730a6b336d7f6494"}, {file = "opentelemetry_exporter_otlp-1.36.0.tar.gz", hash = "sha256:72f166ea5a8923ac42889337f903e93af57db8893de200369b07401e98e4e06b"}, @@ -1106,6 +1163,7 @@ version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, @@ -1120,6 +1178,7 @@ version = "1.36.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf"}, @@ -1143,6 +1202,7 @@ version = "1.36.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, @@ -1163,6 +1223,7 @@ version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, @@ -1177,6 +1238,7 @@ version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, @@ -1193,6 +1255,7 @@ version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, @@ -1208,6 +1271,7 @@ version = "3.11.1" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532"}, {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65"}, @@ -1293,6 +1357,7 @@ files = [ {file = "orjson-3.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:200c3ad7ed8b5d31d49143265dfebd33420c4b61934ead16833b5cd2c3d241be"}, {file = "orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96"}, ] +markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} [[package]] name = "ormsgpack" @@ -1300,6 +1365,7 @@ version = "1.10.0" description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, @@ -1350,6 +1416,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1361,6 +1428,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1372,6 +1440,7 @@ version = "14.7.0" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"}, {file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"}, @@ -1391,6 +1460,7 @@ version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -1407,6 +1477,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1422,6 +1493,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1440,6 +1512,7 @@ version = "6.31.1" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, @@ -1458,10 +1531,12 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "extra == \"langchain\" and platform_python_implementation == \"PyPy\"", dev = "platform_python_implementation == \"PyPy\""} [[package]] name = "pydantic" @@ -1469,6 +1544,7 @@ version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, @@ -1482,7 +1558,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1490,6 +1566,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1601,6 +1678,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1615,6 +1693,7 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -1638,6 +1717,7 @@ version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -1656,6 +1736,7 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1670,6 +1751,7 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1684,6 +1766,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1704,6 +1787,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1759,6 +1843,7 @@ files = [ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "regex" @@ -1766,6 +1851,7 @@ version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, @@ -1862,6 +1948,7 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -1883,10 +1970,12 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" @@ -1897,6 +1986,7 @@ version = "0.5.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, @@ -1924,6 +2014,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1935,6 +2026,8 @@ version = "2.0.42" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "SQLAlchemy-2.0.42-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ee065898359fdee83961aed5cf1fb4cfa913ba71b58b41e036001d90bebbf7a"}, {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bc76d86216443daa2e27e6b04a9b96423f0b69b5d0c40c7f4b9a4cdf7d8d90"}, @@ -2030,10 +2123,12 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx"] @@ -2045,6 +2140,7 @@ version = "0.10.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tiktoken-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1db7a65b196d757d18ef53193957d44549b88f373d4b87db532f04d18193b847"}, {file = "tiktoken-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f55701461267d025597ebb2290d612fe9c5c5fbb625ebf7495c9f0f8e4c30f01"}, @@ -2092,6 +2188,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2133,10 +2231,12 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -2154,6 +2254,7 @@ version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, @@ -2165,6 +2266,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -2179,13 +2281,14 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2196,6 +2299,7 @@ version = "20.33.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, @@ -2208,7 +2312,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "werkzeug" @@ -2216,6 +2320,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2233,6 +2338,7 @@ version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -2321,13 +2427,14 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2340,6 +2447,7 @@ version = "0.23.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, @@ -2439,6 +2547,7 @@ files = [ {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} @@ -2451,6 +2560,6 @@ langchain = ["langchain"] openai = ["openai"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<4.0" content-hash = "746d378f1bbcf8c86346a0ac7a4ebb400462d38e43eee5d7edf51dd78a8307a1" From f90d41b2eb6d18200ee9248a814bfc353bfde4c3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:00:25 +0200 Subject: [PATCH 010/296] fix(langchain): keep trace attributes on non-chain llm runs (#1293) --- langfuse/langchain/CallbackHandler.py | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index edd21747f..03e73c3f2 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -240,7 +240,7 @@ def _register_langfuse_prompt( If parent_run_id is None, we are at the root of a trace and should not attempt to register the prompt, as there will be no LLM invocation following it. Otherwise it would have been traced in with a parent run consisting of the prompt template formatting and the LLM invocation. """ - if not parent_run_id: + if not parent_run_id or not run_id: return langfuse_prompt = metadata and metadata.get("langfuse_prompt", None) @@ -255,7 +255,7 @@ def _register_langfuse_prompt( self.prompt_to_parent_run_map[run_id] = registered_prompt def _deregister_langfuse_prompt(self, run_id: Optional[UUID]) -> None: - if run_id in self.prompt_to_parent_run_map: + if run_id is not None and run_id in self.prompt_to_parent_run_map: del self.prompt_to_parent_run_map[run_id] def on_agent_action( @@ -610,7 +610,14 @@ def __on_llm_action( content = { "name": self.get_langchain_run_name(serialized, **kwargs), "input": prompts, - "metadata": self.__join_tags_and_metadata(tags, metadata), + "metadata": self.__join_tags_and_metadata( + tags, + metadata, + # If llm is run isolated and outside chain, keep trace attributes + keep_langfuse_trace_attributes=True + if parent_run_id is None + else False, + ), "model": model_name, "model_parameters": self._parse_model_parameters(kwargs), "prompt": registered_prompt, @@ -763,16 +770,19 @@ def __join_tags_and_metadata( self, tags: Optional[List[str]] = None, metadata: Optional[Dict[str, Any]] = None, - trace_metadata: Optional[Dict[str, Any]] = None, + keep_langfuse_trace_attributes: bool = False, ) -> Optional[Dict[str, Any]]: final_dict = {} if tags is not None and len(tags) > 0: final_dict["tags"] = tags if metadata is not None: final_dict.update(metadata) - if trace_metadata is not None: - final_dict.update(trace_metadata) - return _strip_langfuse_keys_from_dict(final_dict) if final_dict != {} else None + + return ( + _strip_langfuse_keys_from_dict(final_dict, keep_langfuse_trace_attributes) + if final_dict != {} + else None + ) def _convert_message_to_dict(self, message: BaseMessage) -> Dict[str, Any]: # assistant message @@ -1027,12 +1037,17 @@ def _parse_model_name_from_metadata(metadata: Optional[Dict[str, Any]]) -> Any: return metadata.get("ls_model_name", None) -def _strip_langfuse_keys_from_dict(metadata: Optional[Dict[str, Any]]) -> Any: +def _strip_langfuse_keys_from_dict( + metadata: Optional[Dict[str, Any]], keep_langfuse_trace_attributes: bool +) -> Any: if metadata is None or not isinstance(metadata, dict): return metadata langfuse_metadata_keys = [ "langfuse_prompt", + ] + + langfuse_trace_attribute_keys = [ "langfuse_session_id", "langfuse_user_id", "langfuse_tags", @@ -1043,4 +1058,8 @@ def _strip_langfuse_keys_from_dict(metadata: Optional[Dict[str, Any]]) -> Any: for key in langfuse_metadata_keys: metadata_copy.pop(key, None) + if not keep_langfuse_trace_attributes: + for key in langfuse_trace_attribute_keys: + metadata_copy.pop(key, None) + return metadata_copy From 1addc6a0075aa162cc7382d73773c0154c489431 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:00:51 +0200 Subject: [PATCH 011/296] feat(openai): add support for response.parse (#1292) --- langfuse/openai.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index b8d9ea2d7..96ba14a0c 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -23,7 +23,7 @@ from dataclasses import dataclass from datetime import datetime from inspect import isclass -from typing import Optional, cast, Any +from typing import Any, Optional, cast from openai._types import NotGiven from packaging.version import Version @@ -161,6 +161,22 @@ class OpenAiDefinition: sync=False, min_version="1.66.0", ), + OpenAiDefinition( + module="openai.resources.responses", + object="Responses", + method="parse", + type="chat", + sync=True, + min_version="1.66.0", + ), + OpenAiDefinition( + module="openai.resources.responses", + object="AsyncResponses", + method="parse", + type="chat", + sync=False, + min_version="1.66.0", + ), ] @@ -570,7 +586,10 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: ) curr["arguments"] += getattr(tool_call_chunk, "arguments", "") - elif delta.get("tool_calls", None) is not None and len(delta.get("tool_calls")) > 0: + elif ( + delta.get("tool_calls", None) is not None + and len(delta.get("tool_calls")) > 0 + ): curr = completion["tool_calls"] tool_call_chunk = getattr( delta.get("tool_calls", None)[0], "function", None From 2bfdff29b56143f46fbebbb3815a02333a234bef Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:44:40 +0200 Subject: [PATCH 012/296] chore: release v3.2.4 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 018a1b246..800e5d5a3 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.3" +__version__ = "3.2.4" diff --git a/pyproject.toml b/pyproject.toml index 4f9db2c54..6cc6d0705 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.3" +version = "3.2.4" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 314e00cd8bd129c1060bff71244d27e94d4c486f Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:32:23 +0200 Subject: [PATCH 013/296] chore: specify otlp http exporter dep (#1295) chore: specifiy otlp http exporter dep --- poetry.lock | 948 +++++++++++++++++++------------------------------ pyproject.toml | 2 +- 2 files changed, 365 insertions(+), 585 deletions(-) diff --git a/poetry.lock b/poetry.lock index 007b7fdbd..c3c1217e7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -18,7 +17,6 @@ version = "4.10.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, @@ -39,8 +37,6 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"langchain\" and python_version < \"3.11\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -52,7 +48,6 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -64,7 +59,6 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -76,7 +70,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -146,7 +139,6 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] -markers = {main = "extra == \"langchain\" and platform_python_implementation == \"PyPy\"", dev = "platform_python_implementation == \"PyPy\""} [package.dependencies] pycparser = "*" @@ -157,7 +149,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -165,104 +156,90 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.2" +version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] @@ -271,12 +248,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "extra == \"openai\" and platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -284,7 +259,6 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -296,12 +270,10 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -markers = {main = "extra == \"openai\""} [[package]] name = "exceptiongroup" @@ -309,8 +281,6 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -328,7 +298,6 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -343,7 +312,6 @@ version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -352,7 +320,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "googleapis-common-protos" @@ -360,7 +328,6 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -378,8 +345,6 @@ version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"langchain\"" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -441,77 +406,12 @@ files = [ docs = ["Sphinx", "furo"] test = ["objgraph", "psutil", "setuptools"] -[[package]] -name = "grpcio" -version = "1.74.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907"}, - {file = "grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb"}, - {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486"}, - {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11"}, - {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9"}, - {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc"}, - {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e"}, - {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82"}, - {file = "grpcio-1.74.0-cp310-cp310-win32.whl", hash = "sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7"}, - {file = "grpcio-1.74.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5"}, - {file = "grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31"}, - {file = "grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4"}, - {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce"}, - {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3"}, - {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182"}, - {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d"}, - {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f"}, - {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4"}, - {file = "grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b"}, - {file = "grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11"}, - {file = "grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8"}, - {file = "grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6"}, - {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5"}, - {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49"}, - {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7"}, - {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3"}, - {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707"}, - {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b"}, - {file = "grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c"}, - {file = "grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc"}, - {file = "grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89"}, - {file = "grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01"}, - {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e"}, - {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91"}, - {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249"}, - {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362"}, - {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f"}, - {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20"}, - {file = "grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa"}, - {file = "grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24"}, - {file = "grpcio-1.74.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4bc5fca10aaf74779081e16c2bcc3d5ec643ffd528d9e7b1c9039000ead73bae"}, - {file = "grpcio-1.74.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:6bab67d15ad617aff094c382c882e0177637da73cbc5532d52c07b4ee887a87b"}, - {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:655726919b75ab3c34cdad39da5c530ac6fa32696fb23119e36b64adcfca174a"}, - {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a2b06afe2e50ebfd46247ac3ba60cac523f54ec7792ae9ba6073c12daf26f0a"}, - {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f251c355167b2360537cf17bea2cf0197995e551ab9da6a0a59b3da5e8704f9"}, - {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f7b5882fb50632ab1e48cb3122d6df55b9afabc265582808036b6e51b9fd6b7"}, - {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:834988b6c34515545b3edd13e902c1acdd9f2465d386ea5143fb558f153a7176"}, - {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22b834cef33429ca6cc28303c9c327ba9a3fafecbf62fae17e9a7b7163cc43ac"}, - {file = "grpcio-1.74.0-cp39-cp39-win32.whl", hash = "sha256:7d95d71ff35291bab3f1c52f52f474c632db26ea12700c2ff0ea0532cb0b5854"}, - {file = "grpcio-1.74.0-cp39-cp39-win_amd64.whl", hash = "sha256:ecde9ab49f58433abe02f9ed076c7b5be839cf0153883a6d23995937a82392fa"}, - {file = "grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.74.0)"] - [[package]] name = "h11" version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -523,7 +423,6 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -545,7 +444,6 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -558,7 +456,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -566,14 +464,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" -version = "2.6.12" +version = "2.6.13" description = "File identification library for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, - {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, ] [package.extras] @@ -585,7 +482,6 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -600,7 +496,6 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -610,12 +505,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -624,7 +519,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -636,7 +530,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -654,7 +547,6 @@ version = "0.10.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, @@ -734,7 +626,6 @@ files = [ {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] -markers = {main = "extra == \"openai\""} [[package]] name = "jsonpatch" @@ -742,12 +633,10 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -758,12 +647,10 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] -markers = {main = "extra == \"langchain\""} [[package]] name = "langchain" @@ -771,8 +658,6 @@ version = "0.3.27" description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "extra == \"langchain\"" files = [ {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, @@ -813,12 +698,10 @@ version = "0.3.74" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.74-py3-none-any.whl", hash = "sha256:088338b5bc2f6a66892f9afc777992c24ee3188f41cbc603d09181e34a228ce7"}, {file = "langchain_core-0.3.74.tar.gz", hash = "sha256:ff604441aeade942fbcc0a3860a592daba7671345230c2078ba2eb5f82b6ba76"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" @@ -835,7 +718,6 @@ version = "0.2.14" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0,>=3.9" -groups = ["dev"] files = [ {file = "langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8"}, {file = "langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978"}, @@ -852,8 +734,6 @@ version = "0.3.9" description = "LangChain text splitting utilities" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"langchain\"" files = [ {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, @@ -868,7 +748,6 @@ version = "0.2.76" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = "<4.0,>=3.9.0" -groups = ["dev"] files = [ {file = "langgraph-0.2.76-py3-none-any.whl", hash = "sha256:076b8b5d2fc5a9761c46a7618430cfa5c978a8012257c43cbc127b27e0fd7872"}, {file = "langgraph-0.2.76.tar.gz", hash = "sha256:688f8dcd9b6797ba78384599e0de944773000c75156ad1e186490e99e89fa5c0"}, @@ -885,7 +764,6 @@ version = "2.1.1" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, @@ -901,7 +779,6 @@ version = "0.1.74" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph_sdk-0.1.74-py3-none-any.whl", hash = "sha256:3a265c3757fe0048adad4391d10486db63ef7aa5a2cbd22da22d4503554cb890"}, {file = "langgraph_sdk-0.1.74.tar.gz", hash = "sha256:7450e0db5b226cc2e5328ca22c5968725873630ef47c4206a30707cb25dc3ad6"}, @@ -913,16 +790,14 @@ orjson = ">=3.10.1" [[package]] name = "langsmith" -version = "0.4.13" +version = "0.4.14" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "langsmith-0.4.13-py3-none-any.whl", hash = "sha256:dab7b16ee16986995007bf5a777f45c18f8bf7453f67ae2ebcb46ce43c214297"}, - {file = "langsmith-0.4.13.tar.gz", hash = "sha256:1ae7dbb5d8150647406f49885a2dd16ab12bd990254b5dc23718838b3d086fde"}, + {file = "langsmith-0.4.14-py3-none-any.whl", hash = "sha256:b6d070ac425196947d2a98126fb0e35f3b8c001a2e6e5b7049dd1c56f0767d0b"}, + {file = "langsmith-0.4.14.tar.gz", hash = "sha256:4d29c7a9c85b20ba813ab9c855407bccdf5eb4f397f512ffa89959b2a2cb83ed"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -946,7 +821,6 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1017,7 +891,6 @@ version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, @@ -1078,7 +951,6 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1090,7 +962,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1102,12 +973,10 @@ version = "1.99.9" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a"}, {file = "openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -1131,7 +1000,6 @@ version = "1.36.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, @@ -1141,29 +1009,12 @@ files = [ importlib-metadata = ">=6.0,<8.8.0" typing-extensions = ">=4.5.0" -[[package]] -name = "opentelemetry-exporter-otlp" -version = "1.36.0" -description = "OpenTelemetry Collector Exporters" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp-1.36.0-py3-none-any.whl", hash = "sha256:de93b7c45bcc78296998775d52add7c63729e83ef2cd6560730a6b336d7f6494"}, - {file = "opentelemetry_exporter_otlp-1.36.0.tar.gz", hash = "sha256:72f166ea5a8923ac42889337f903e93af57db8893de200369b07401e98e4e06b"}, -] - -[package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.36.0" -opentelemetry-exporter-otlp-proto-http = "1.36.0" - [[package]] name = "opentelemetry-exporter-otlp-proto-common" version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, @@ -1172,37 +1023,12 @@ files = [ [package.dependencies] opentelemetry-proto = "1.36.0" -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.36.0" -description = "OpenTelemetry Collector Protobuf over gRPC Exporter" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.57,<2.0" -grpcio = [ - {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, - {version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""}, -] -opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.36.0" -opentelemetry-proto = "1.36.0" -opentelemetry-sdk = ">=1.36.0,<1.37.0" -typing-extensions = ">=4.6.0" - [[package]] name = "opentelemetry-exporter-otlp-proto-http" version = "1.36.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, @@ -1223,7 +1049,6 @@ version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, @@ -1238,7 +1063,6 @@ version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, @@ -1255,7 +1079,6 @@ version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, @@ -1267,97 +1090,95 @@ typing-extensions = ">=4.5.0" [[package]] name = "orjson" -version = "3.11.1" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +version = "3.11.2" +description = "" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532"}, - {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65"}, - {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5caf7f13f2e1b4e137060aed892d4541d07dabc3f29e6d891e2383c7ed483440"}, - {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f716bcc166524eddfcf9f13f8209ac19a7f27b05cf591e883419079d98c8c99d"}, - {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507d6012fab05465d8bf21f5d7f4635ba4b6d60132874e349beff12fb51af7fe"}, - {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1545083b0931f754c80fd2422a73d83bea7a6d1b6de104a5f2c8dd3d64c291e"}, - {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e217ce3bad76351e1eb29ebe5ca630326f45cd2141f62620107a229909501a3"}, - {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ef26e009304bda4df42e4afe518994cde6f89b4b04c0ff24021064f83f4fbb"}, - {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ba49683b87bea3ae1489a88e766e767d4f423a669a61270b6d6a7ead1c33bd65"}, - {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5072488fcc5cbcda2ece966d248e43ea1d222e19dd4c56d3f82747777f24d864"}, - {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f58ae2bcd119226fe4aa934b5880fe57b8e97b69e51d5d91c88a89477a307016"}, - {file = "orjson-3.11.1-cp310-cp310-win32.whl", hash = "sha256:6723be919c07906781b9c63cc52dc7d2fb101336c99dd7e85d3531d73fb493f7"}, - {file = "orjson-3.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:5fd44d69ddfdfb4e8d0d83f09d27a4db34930fba153fbf79f8d4ae8b47914e04"}, - {file = "orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef"}, - {file = "orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd"}, - {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c"}, - {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a"}, - {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09"}, - {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27"}, - {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495"}, - {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c"}, - {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d"}, - {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84"}, - {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39"}, - {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55"}, - {file = "orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17"}, - {file = "orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313"}, - {file = "orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af"}, - {file = "orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910"}, - {file = "orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d"}, - {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5"}, - {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b"}, - {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf"}, - {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56"}, - {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289"}, - {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81"}, - {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968"}, - {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a"}, - {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d"}, - {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4"}, - {file = "orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf"}, - {file = "orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae"}, - {file = "orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d"}, - {file = "orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e"}, - {file = "orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064"}, - {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6"}, - {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894"}, - {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912"}, - {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2"}, - {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8"}, - {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4"}, - {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24"}, - {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014"}, - {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058"}, - {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f"}, - {file = "orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092"}, - {file = "orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77"}, - {file = "orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4"}, - {file = "orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b"}, - {file = "orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e"}, - {file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727"}, - {file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c"}, - {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037"}, - {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa"}, - {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de"}, - {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45"}, - {file = "orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1"}, - {file = "orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8"}, - {file = "orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1"}, - {file = "orjson-3.11.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3d593a9e0bccf2c7401ae53625b519a7ad7aa555b1c82c0042b322762dc8af4e"}, - {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baad413c498fc1eef568504f11ea46bc71f94b845c075e437da1e2b85b4fb86"}, - {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22cf17ae1dae3f9b5f37bfcdba002ed22c98bbdb70306e42dc18d8cc9b50399a"}, - {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e855c1e97208133ce88b3ef6663c9a82ddf1d09390cd0856a1638deee0390c3c"}, - {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5861c5f7acff10599132854c70ab10abf72aebf7c627ae13575e5f20b1ab8fe"}, - {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1e6415c5b5ff3a616a6dafad7b6ec303a9fc625e9313c8e1268fb1370a63dcb"}, - {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912579642f5d7a4a84d93c5eed8daf0aa34e1f2d3f4dc6571a8e418703f5701e"}, - {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2092e1d3b33f64e129ff8271642afddc43763c81f2c30823b4a4a4a5f2ea5b55"}, - {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:b8ac64caba1add2c04e9cd4782d4d0c4d6c554b7a3369bdec1eed7854c98db7b"}, - {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:23196b826ebc85c43f8e27bee0ab33c5fb13a29ea47fb4fcd6ebb1e660eb0252"}, - {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f2d3364cfad43003f1e3d564a069c8866237cca30f9c914b26ed2740b596ed00"}, - {file = "orjson-3.11.1-cp39-cp39-win32.whl", hash = "sha256:20b0dca94ea4ebe4628330de50975b35817a3f52954c1efb6d5d0498a3bbe581"}, - {file = "orjson-3.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:200c3ad7ed8b5d31d49143265dfebd33420c4b61934ead16833b5cd2c3d241be"}, - {file = "orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96"}, -] -markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} +files = [ + {file = "orjson-3.11.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6b8a78c33496230a60dc9487118c284c15ebdf6724386057239641e1eb69761"}, + {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc04036eeae11ad4180d1f7b5faddb5dab1dee49ecd147cd431523869514873b"}, + {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c04325839c5754c253ff301cee8aaed7442d974860a44447bb3be785c411c27"}, + {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32769e04cd7fdc4a59854376211145a1bbbc0aea5e9d6c9755d3d3c301d7c0df"}, + {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff285d14917ea1408a821786e3677c5261fa6095277410409c694b8e7720ae0"}, + {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2662f908114864b63ff75ffe6ffacf996418dd6cc25e02a72ad4bda81b1ec45a"}, + {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab463cf5d08ad6623a4dac1badd20e88a5eb4b840050c4812c782e3149fe2334"}, + {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:64414241bde943cbf3c00d45fcb5223dca6d9210148ba984aae6b5d63294502b"}, + {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7773e71c0ae8c9660192ff144a3d69df89725325e3d0b6a6bb2c50e5ebaf9b84"}, + {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:652ca14e283b13ece35bf3a86503c25592f294dbcfc5bb91b20a9c9a62a3d4be"}, + {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:26e99e98df8990ecfe3772bbdd7361f602149715c2cbc82e61af89bfad9528a4"}, + {file = "orjson-3.11.2-cp310-cp310-win32.whl", hash = "sha256:5814313b3e75a2be7fe6c7958201c16c4560e21a813dbad25920752cecd6ad66"}, + {file = "orjson-3.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc471ce2225ab4c42ca672f70600d46a8b8e28e8d4e536088c1ccdb1d22b35ce"}, + {file = "orjson-3.11.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:888b64ef7eaeeff63f773881929434a5834a6a140a63ad45183d59287f07fc6a"}, + {file = "orjson-3.11.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:83387cc8b26c9fa0ae34d1ea8861a7ae6cff8fb3e346ab53e987d085315a728e"}, + {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e35f003692c216d7ee901b6b916b5734d6fc4180fcaa44c52081f974c08e17"}, + {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a0a4c29ae90b11d0c00bcc31533854d89f77bde2649ec602f512a7e16e00640"}, + {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:585d712b1880f68370108bc5534a257b561672d1592fae54938738fe7f6f1e33"}, + {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d08e342a7143f8a7c11f1c4033efe81acbd3c98c68ba1b26b96080396019701f"}, + {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c0f84fc50398773a702732c87cd622737bf11c0721e6db3041ac7802a686fb"}, + {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:140f84e3c8d4c142575898c91e3981000afebf0333df753a90b3435d349a5fe5"}, + {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96304a2b7235e0f3f2d9363ddccdbfb027d27338722fe469fe656832a017602e"}, + {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d7612bb227d5d9582f1f50a60bd55c64618fc22c4a32825d233a4f2771a428a"}, + {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a134587d18fe493befc2defffef2a8d27cfcada5696cb7234de54a21903ae89a"}, + {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b84455e60c4bc12c1e4cbaa5cfc1acdc7775a9da9cec040e17232f4b05458bd"}, + {file = "orjson-3.11.2-cp311-cp311-win32.whl", hash = "sha256:f0660efeac223f0731a70884e6914a5f04d613b5ae500744c43f7bf7b78f00f9"}, + {file = "orjson-3.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:955811c8405251d9e09cbe8606ad8fdef49a451bcf5520095a5ed38c669223d8"}, + {file = "orjson-3.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:2e4d423a6f838552e3a6d9ec734b729f61f88b1124fd697eab82805ea1a2a97d"}, + {file = "orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486"}, + {file = "orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1"}, + {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131"}, + {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c"}, + {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14"}, + {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448"}, + {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c"}, + {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804"}, + {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307"}, + {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219"}, + {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45"}, + {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e"}, + {file = "orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e"}, + {file = "orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732"}, + {file = "orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36"}, + {file = "orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219"}, + {file = "orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad"}, + {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2"}, + {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe"}, + {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae"}, + {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6"}, + {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1"}, + {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa"}, + {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e"}, + {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15"}, + {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac"}, + {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8"}, + {file = "orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5"}, + {file = "orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d"}, + {file = "orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535"}, + {file = "orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7"}, + {file = "orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81"}, + {file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f"}, + {file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7"}, + {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4"}, + {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f"}, + {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7"}, + {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6"}, + {file = "orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f"}, + {file = "orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8"}, + {file = "orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67"}, + {file = "orjson-3.11.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:957f10c7b5bce3d3f2ad577f3b307c784f5dabafcce3b836229c269c11841c86"}, + {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a669e31ab8eb466c9142ac7a4be2bb2758ad236a31ef40dcd4cf8774ab40f33"}, + {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adedf7d887416c51ad49de3c53b111887e0b63db36c6eb9f846a8430952303d8"}, + {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad8873979659ad98fc56377b9c5b93eb8059bf01e6412f7abf7dbb3d637a991"}, + {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9482ef83b2bf796157566dd2d2742a8a1e377045fe6065fa67acb1cb1d21d9a3"}, + {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73cee7867c1fcbd1cc5b6688b3e13db067f968889242955780123a68b3d03316"}, + {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:465166773265f3cc25db10199f5d11c81898a309e26a2481acf33ddbec433fda"}, + {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc000190a7b1d2d8e36cba990b3209a1e15c0efb6c7750e87f8bead01afc0d46"}, + {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:df3fdd8efa842ccbb81135d6f58a73512f11dba02ed08d9466261c2e9417af4e"}, + {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3dacfc621be3079ec69e0d4cb32e3764067726e0ef5a5576428f68b6dc85b4f6"}, + {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9fdff73a029cde5f4a1cf5ec9dbc6acab98c9ddd69f5580c2b3f02ce43ba9f9f"}, + {file = "orjson-3.11.2-cp39-cp39-win32.whl", hash = "sha256:b1efbdc479c6451138c3733e415b4d0e16526644e54e2f3689f699c4cda303bf"}, + {file = "orjson-3.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:c9ec0cc0d4308cad1e38a1ee23b64567e2ff364c2a3fe3d6cbc69cf911c45712"}, + {file = "orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309"}, +] [[package]] name = "ormsgpack" @@ -1365,7 +1186,6 @@ version = "1.10.0" description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, @@ -1416,7 +1236,6 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1428,7 +1247,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1440,7 +1258,6 @@ version = "14.7.0" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"}, {file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"}, @@ -1460,7 +1277,6 @@ version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -1477,7 +1293,6 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1493,7 +1308,6 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1512,7 +1326,6 @@ version = "6.31.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, @@ -1531,12 +1344,10 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] -markers = {main = "extra == \"langchain\" and platform_python_implementation == \"PyPy\"", dev = "platform_python_implementation == \"PyPy\""} [[package]] name = "pydantic" @@ -1544,7 +1355,6 @@ version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, @@ -1558,7 +1368,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -1566,7 +1376,6 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1678,7 +1487,6 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1693,7 +1501,6 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -1717,7 +1524,6 @@ version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -1736,7 +1542,6 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1751,7 +1556,6 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1766,7 +1570,6 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1787,7 +1590,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1843,7 +1645,6 @@ files = [ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] -markers = {main = "extra == \"langchain\""} [[package]] name = "regex" @@ -1851,7 +1652,6 @@ version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, @@ -1948,7 +1748,6 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -1970,12 +1769,10 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" @@ -1986,7 +1783,6 @@ version = "0.5.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, @@ -2014,7 +1810,6 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2022,70 +1817,68 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.42" +version = "2.0.43" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"langchain\"" -files = [ - {file = "SQLAlchemy-2.0.42-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ee065898359fdee83961aed5cf1fb4cfa913ba71b58b41e036001d90bebbf7a"}, - {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bc76d86216443daa2e27e6b04a9b96423f0b69b5d0c40c7f4b9a4cdf7d8d90"}, - {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89143290fb94c50a8dec73b06109ccd245efd8011d24fc0ddafe89dc55b36651"}, - {file = "SQLAlchemy-2.0.42-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:4efbdc9754c7145a954911bfeef815fb0843e8edab0e9cecfa3417a5cbd316af"}, - {file = "SQLAlchemy-2.0.42-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:88f8a8007a658dfd82c16a20bd9673ae6b33576c003b5166d42697d49e496e61"}, - {file = "SQLAlchemy-2.0.42-cp37-cp37m-win32.whl", hash = "sha256:c5dd245e6502990ccf612d51f220a7b04cbea3f00f6030691ffe27def76ca79b"}, - {file = "SQLAlchemy-2.0.42-cp37-cp37m-win_amd64.whl", hash = "sha256:5651eb19cacbeb2fe7431e4019312ed00a0b3fbd2d701423e0e2ceaadb5bcd9f"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:172b244753e034d91a826f80a9a70f4cbac690641207f2217f8404c261473efe"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be28f88abd74af8519a4542185ee80ca914933ca65cdfa99504d82af0e4210df"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b344859d282fde388047f1710860bb23f4098f705491e06b8ab52a48aafea9"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97978d223b11f1d161390a96f28c49a13ce48fdd2fed7683167c39bdb1b8aa09"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e35b9b000c59fcac2867ab3a79fc368a6caca8706741beab3b799d47005b3407"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc7347ad7a7b1c78b94177f2d57263113bb950e62c59b96ed839b131ea4234e1"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-win32.whl", hash = "sha256:739e58879b20a179156b63aa21f05ccacfd3e28e08e9c2b630ff55cd7177c4f1"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-win_amd64.whl", hash = "sha256:1aef304ada61b81f1955196f584b9e72b798ed525a7c0b46e09e98397393297b"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c34100c0b7ea31fbc113c124bcf93a53094f8951c7bf39c45f39d327bad6d1e7"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad59dbe4d1252448c19d171dfba14c74e7950b46dc49d015722a4a06bfdab2b0"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9187498c2149919753a7fd51766ea9c8eecdec7da47c1b955fa8090bc642eaa"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f092cf83ebcafba23a247f5e03f99f5436e3ef026d01c8213b5eca48ad6efa9"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc6afee7e66fdba4f5a68610b487c1f754fccdc53894a9567785932dbb6a265e"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:260ca1d2e5910f1f1ad3fe0113f8fab28657cee2542cb48c2f342ed90046e8ec"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-win32.whl", hash = "sha256:2eb539fd83185a85e5fcd6b19214e1c734ab0351d81505b0f987705ba0a1e231"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-win_amd64.whl", hash = "sha256:9193fa484bf00dcc1804aecbb4f528f1123c04bad6a08d7710c909750fa76aeb"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09637a0872689d3eb71c41e249c6f422e3e18bbd05b4cd258193cfc7a9a50da2"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3cb3ec67cc08bea54e06b569398ae21623534a7b1b23c258883a7c696ae10df"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87e6a5ef6f9d8daeb2ce5918bf5fddecc11cae6a7d7a671fcc4616c47635e01"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b718011a9d66c0d2f78e1997755cd965f3414563b31867475e9bc6efdc2281d"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16d9b544873fe6486dddbb859501a07d89f77c61d29060bb87d0faf7519b6a4d"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21bfdf57abf72fa89b97dd74d3187caa3172a78c125f2144764a73970810c4ee"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-win32.whl", hash = "sha256:78b46555b730a24901ceb4cb901c6b45c9407f8875209ed3c5d6bcd0390a6ed1"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-win_amd64.whl", hash = "sha256:4c94447a016f36c4da80072e6c6964713b0af3c8019e9c4daadf21f61b81ab53"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed5a6959b1668d97a32e3fd848b485f65ee3c05a759dee06d90e4545a3c77f1e"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2ddbaafe32f0dd12d64284b1c3189104b784c9f3dba8cc1ba7e642e2b14b906f"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37f4f42568b6c656ee177b3e111d354b5dda75eafe9fe63492535f91dfa35829"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb57923d852d38671a17abda9a65cc59e3e5eab51fb8307b09de46ed775bcbb8"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:437c2a8b0c780ff8168a470beb22cb4a25e1c63ea6a7aec87ffeb07aa4b76641"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:480f7df62f0b3ad6aa011eefa096049dc1770208bb71f234959ee2864206eefe"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-win32.whl", hash = "sha256:d119c80c614d62d32e236ae68e21dd28a2eaf070876b2f28a6075d5bae54ef3f"}, - {file = "sqlalchemy-2.0.42-cp38-cp38-win_amd64.whl", hash = "sha256:be3a02f963c8d66e28bb4183bebab66dc4379701d92e660f461c65fecd6ff399"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78548fd65cd76d4c5a2e6b5f245d7734023ee4de33ee7bb298f1ac25a9935e0d"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf4bf5a174d8a679a713b7a896470ffc6baab78e80a79e7ec5668387ffeccc8b"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c7ff7ba08b375f8a8fa0511e595c9bdabb5494ec68f1cf69bb24e54c0d90f2"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b3c117f65d64e806ce5ce9ce578f06224dc36845e25ebd2554b3e86960e1aed"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:27e4a7b3a7a61ff919c2e7caafd612f8626114e6e5ebbe339de3b5b1df9bc27e"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b01e0dd39f96aefda5ab002d8402db4895db871eb0145836246ce0661635ce55"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-win32.whl", hash = "sha256:49362193b1f43aa158deebf438062d7b5495daa9177c6c5d0f02ceeb64b544ea"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-win_amd64.whl", hash = "sha256:636ec3dc83b2422a7ff548d0f8abf9c23742ca50e2a5cdc492a151eac7a0248b"}, - {file = "sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835"}, - {file = "sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f"}, +files = [ + {file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"}, + {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"}, + {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07097c0a1886c150ef2adba2ff7437e84d40c0f7dcb44a2c2b9c905ccfc6361c"}, + {file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cdeff998cb294896a34e5b2f00e383e7c5c4ef3b4bfa375d9104723f15186443"}, + {file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bcf0724a62a5670e5718957e05c56ec2d6850267ea859f8ad2481838f889b42c"}, + {file = "SQLAlchemy-2.0.43-cp37-cp37m-win32.whl", hash = "sha256:c697575d0e2b0a5f0433f679bda22f63873821d991e95a90e9e52aae517b2e32"}, + {file = "SQLAlchemy-2.0.43-cp37-cp37m-win_amd64.whl", hash = "sha256:d34c0f6dbefd2e816e8f341d0df7d4763d382e3f452423e752ffd1e213da2512"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70322986c0c699dca241418fcf18e637a4369e0ec50540a2b907b184c8bca069"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87accdbba88f33efa7b592dc2e8b2a9c2cdbca73db2f9d5c510790428c09c154"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c00e7845d2f692ebfc7d5e4ec1a3fd87698e4337d09e58d6749a16aedfdf8612"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:022e436a1cb39b13756cf93b48ecce7aa95382b9cfacceb80a7d263129dfd019"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5e73ba0d76eefc82ec0219d2301cb33bfe5205ed7a2602523111e2e56ccbd20"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c2e02f06c68092b875d5cbe4824238ab93a7fa35d9c38052c033f7ca45daa18"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-win32.whl", hash = "sha256:e7a903b5b45b0d9fa03ac6a331e1c1d6b7e0ab41c63b6217b3d10357b83c8b00"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-win_amd64.whl", hash = "sha256:4bf0edb24c128b7be0c61cd17eef432e4bef507013292415f3fb7023f02b7d4b"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e6aeb2e0932f32950cf56a8b4813cb15ff792fc0c9b3752eaf067cfe298496a"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f964a05356f4bca4112e6334ed7c208174511bd56e6b8fc86dad4d024d4185"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46293c39252f93ea0910aababa8752ad628bcce3a10d3f260648dd472256983f"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136063a68644eca9339d02e6693932116f6a8591ac013b0014479a1de664e40a"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6e2bf13d9256398d037fef09fd8bf9b0bf77876e22647d10761d35593b9ac547"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:44337823462291f17f994d64282a71c51d738fc9ef561bf265f1d0fd9116a782"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-win32.whl", hash = "sha256:13194276e69bb2af56198fef7909d48fd34820de01d9c92711a5fa45497cc7ed"}, + {file = "sqlalchemy-2.0.43-cp38-cp38-win_amd64.whl", hash = "sha256:334f41fa28de9f9be4b78445e68530da3c5fa054c907176460c81494f4ae1f5e"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb5c832cc30663aeaf5e39657712f4c4241ad1f638d487ef7216258f6d41fe7"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11f43c39b4b2ec755573952bbcc58d976779d482f6f832d7f33a8d869ae891bf"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413391b2239db55be14fa4223034d7e13325a1812c8396ecd4f2c08696d5ccad"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c379e37b08c6c527181a397212346be39319fb64323741d23e46abd97a400d34"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03d73ab2a37d9e40dec4984d1813d7878e01dbdc742448d44a7341b7a9f408c7"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cee08f15d9e238ede42e9bbc1d6e7158d0ca4f176e4eab21f88ac819ae3bd7b"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-win32.whl", hash = "sha256:b3edaec7e8b6dc5cd94523c6df4f294014df67097c8217a89929c99975811414"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-win_amd64.whl", hash = "sha256:227119ce0a89e762ecd882dc661e0aa677a690c914e358f0dd8932a2e8b2765b"}, + {file = "sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc"}, + {file = "sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417"}, ] [package.dependencies] @@ -2123,12 +1916,10 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] -markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx"] @@ -2136,43 +1927,42 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tiktoken" -version = "0.10.0" +version = "0.11.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "tiktoken-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1db7a65b196d757d18ef53193957d44549b88f373d4b87db532f04d18193b847"}, - {file = "tiktoken-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f55701461267d025597ebb2290d612fe9c5c5fbb625ebf7495c9f0f8e4c30f01"}, - {file = "tiktoken-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83693279af9e8deac0363cbf21dc3d666807f22dcc1091f51e69e6fe6433f71"}, - {file = "tiktoken-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f973bdeb68d645f73dcce60c7795cb5c6f8b7f3dcf92c40c39ad4aee398c075"}, - {file = "tiktoken-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d5cab2a8e8974d6c9744264e94886a58b9087a39737c53fd65dbe2fe8522e719"}, - {file = "tiktoken-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a49d2aedf911b68f23c9c84abce8eaf569cb02613f307cee4fb1bebd8c55ae9"}, - {file = "tiktoken-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:29f55c6b7c9a6a7ea953691c3962ee8fe4ee2f0ceb2a3fded3925acfc45e4b0a"}, - {file = "tiktoken-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f641d0735059b48252c4409d6546b6a62edeb4c4e48306499db11bbe403b872f"}, - {file = "tiktoken-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76e03676b36c2a5e5304b428cff48eb86a033af55212d41a6ac6faec25b2ad"}, - {file = "tiktoken-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ecbd4dc4db3b82ca3421f50d1207d750de1a2856e6ca0544294fe58d2853"}, - {file = "tiktoken-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d2e032cd44700a9ed511c50b9f302bb9e9e77e2ebf8e2d9b8ec12ce64ebf00c6"}, - {file = "tiktoken-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:eb520960fa1788caf033489e3b63bb675c0f321db40569b2d3ca24d7fab5ca72"}, - {file = "tiktoken-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c27021a605b2778af07f84a33fe7c74b5c960a8cfac5d2a7a1075ed592cbe4"}, - {file = "tiktoken-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:45d654f049b4f7ed617ad0c66462c15cc41e5db120f37f122d5b57ffa2aec062"}, - {file = "tiktoken-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34f73d93641b9cc73e19b18dbf9ce1baf086f1c4e18cfa36b952956843bca23c"}, - {file = "tiktoken-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fbfc5dda624c0f85c56bbf8f2ffb42964c798fc6fc1c321a452bf31d4ba21f5"}, - {file = "tiktoken-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:853671af10636b581d632fe7b64b755301c73bc1f2ce837a8cdd9a44ab51f846"}, - {file = "tiktoken-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:56c4e7544be63c78265c82857b9f2a4e2190ae29541fe3467708fc7aabcb1522"}, - {file = "tiktoken-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10e779827ddb0490a415d09d95f8e7a982fd8e14f88c4c2348358f722af3c415"}, - {file = "tiktoken-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:783892cdbec0d33f0f51373d8969e059ab1244af531a52338f702131a032a116"}, - {file = "tiktoken-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43f38d62b6a62a7c82be3770fcdc24cdf5bff3bdf718909d038b804ac774a025"}, - {file = "tiktoken-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72d0b30e08f1e856fa76ee423a3d3f0b1bc984cb6e86a21dbd7c5eee8f67f8f5"}, - {file = "tiktoken-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa840eba09a0c1db0b436baf6cfe1ab7906f33fb663cf96f772a818bad5856a6"}, - {file = "tiktoken-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:642b8d9f266004629ea2457aa29a9eed4f1a031fd804f8c489edbf32df7026c3"}, - {file = "tiktoken-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28fcb8555bb3e9cc6d625385b688b9781e7eb022e76826e721ad93198920034b"}, - {file = "tiktoken-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8e20f7da9086cb5b88eb53ef7ee2fd2db9f3b55c0bcddad51d1ad001a6fcf07"}, - {file = "tiktoken-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f795e7c451d560142c2d88d239ae9fcc11f0b4647376db625c9d4be8f16da45"}, - {file = "tiktoken-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8b3a5717679abb3e8a2097d9577e4c69b975c7fc0381d42598fa6f36f040fc3"}, - {file = "tiktoken-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b061e5035a561a18aefcd969079f1952b4caee3bb204ecfadb4bb41b81b8331b"}, - {file = "tiktoken-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a08250ef9b688c84a7f013fee01d587e73aaf41839bcd564105535276c55122b"}, - {file = "tiktoken-0.10.0.tar.gz", hash = "sha256:7cd88c11699b18081822e6ae1beee55e8f20ea361d73c507d33f5a89a1898f1c"}, +files = [ + {file = "tiktoken-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917"}, + {file = "tiktoken-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0"}, + {file = "tiktoken-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10331d08b5ecf7a780b4fe4d0281328b23ab22cdb4ff65e68d56caeda9940ecc"}, + {file = "tiktoken-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b062c82300341dc87e0258c69f79bed725f87e753c21887aea90d272816be882"}, + {file = "tiktoken-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:195d84bec46169af3b1349a1495c151d37a0ff4cba73fd08282736be7f92cc6c"}, + {file = "tiktoken-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe91581b0ecdd8783ce8cb6e3178f2260a3912e8724d2f2d49552b98714641a1"}, + {file = "tiktoken-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ae374c46afadad0f501046db3da1b36cd4dfbfa52af23c998773682446097cf"}, + {file = "tiktoken-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25a512ff25dc6c85b58f5dd4f3d8c674dc05f96b02d66cdacf628d26a4e4866b"}, + {file = "tiktoken-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2130127471e293d385179c1f3f9cd445070c0772be73cdafb7cec9a3684c0458"}, + {file = "tiktoken-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e43022bf2c33f733ea9b54f6a3f6b4354b909f5a73388fb1b9347ca54a069c"}, + {file = "tiktoken-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:adb4e308eb64380dc70fa30493e21c93475eaa11669dea313b6bbf8210bfd013"}, + {file = "tiktoken-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ece6b76bfeeb61a125c44bbefdfccc279b5288e6007fbedc0d32bfec602df2f2"}, + {file = "tiktoken-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fd9e6b23e860973cf9526544e220b223c60badf5b62e80a33509d6d40e6c8f5d"}, + {file = "tiktoken-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76d53cee2da71ee2731c9caa747398762bda19d7f92665e882fef229cb0b5b"}, + {file = "tiktoken-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef72aab3ea240646e642413cb363b73869fed4e604dcfd69eec63dc54d603e8"}, + {file = "tiktoken-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f929255c705efec7a28bf515e29dc74220b2f07544a8c81b8d69e8efc4578bd"}, + {file = "tiktoken-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61f1d15822e4404953d499fd1dcc62817a12ae9fb1e4898033ec8fe3915fdf8e"}, + {file = "tiktoken-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:45927a71ab6643dfd3ef57d515a5db3d199137adf551f66453be098502838b0f"}, + {file = "tiktoken-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a5f3f25ffb152ee7fec78e90a5e5ea5b03b4ea240beed03305615847f7a6ace2"}, + {file = "tiktoken-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dc6e9ad16a2a75b4c4be7208055a1f707c9510541d94d9cc31f7fbdc8db41d8"}, + {file = "tiktoken-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a0517634d67a8a48fd4a4ad73930c3022629a85a217d256a6e9b8b47439d1e4"}, + {file = "tiktoken-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fb4effe60574675118b73c6fbfd3b5868e5d7a1f570d6cc0d18724b09ecf318"}, + {file = "tiktoken-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94f984c9831fd32688aef4348803b0905d4ae9c432303087bae370dc1381a2b8"}, + {file = "tiktoken-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2177ffda31dec4023356a441793fed82f7af5291120751dee4d696414f54db0c"}, + {file = "tiktoken-0.11.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:13220f12c9e82e399377e768640ddfe28bea962739cc3a869cad98f42c419a89"}, + {file = "tiktoken-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f2db627f5c74477c0404b4089fd8a28ae22fa982a6f7d9c7d4c305c375218f3"}, + {file = "tiktoken-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2302772f035dceb2bcf8e55a735e4604a0b51a6dd50f38218ff664d46ec43807"}, + {file = "tiktoken-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b977989afe44c94bcc50db1f76971bb26dca44218bd203ba95925ef56f8e7a"}, + {file = "tiktoken-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:669a1aa1ad6ebf1b3c26b45deb346f345da7680f845b5ea700bba45c20dea24c"}, + {file = "tiktoken-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:e363f33c720a055586f730c00e330df4c7ea0024bf1c83a8a9a9dbc054c4f304"}, + {file = "tiktoken-0.11.0.tar.gz", hash = "sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a"}, ] [package.dependencies] @@ -2188,8 +1978,6 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2231,12 +2019,10 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -2254,7 +2040,6 @@ version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, @@ -2266,7 +2051,6 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -2281,38 +2065,37 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.33.1" +version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, - {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, + {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, + {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "werkzeug" @@ -2320,7 +2103,6 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2334,91 +2116,92 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wrapt" -version = "1.17.2" +version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, - {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, - {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, - {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, - {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, - {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, - {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, - {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, - {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, - {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, - {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, - {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, - {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, - {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, - {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, - {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, - {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +files = [ + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, + {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, + {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, + {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, + {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, + {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, + {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, + {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, + {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, + {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, + {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, + {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, + {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, + {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, + {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, + {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, + {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, + {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, + {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, + {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, + {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, + {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, + {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, + {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, ] [[package]] @@ -2427,14 +2210,13 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2447,7 +2229,6 @@ version = "0.23.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, @@ -2547,7 +2328,6 @@ files = [ {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} @@ -2560,6 +2340,6 @@ langchain = ["langchain"] openai = ["openai"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "746d378f1bbcf8c86346a0ac7a4ebb400462d38e43eee5d7edf51dd78a8307a1" +content-hash = "d0356916b5c7adba0ed8ea357edb8adcde09269bd83613df67976cd93a246aa3" diff --git a/pyproject.toml b/pyproject.toml index 6cc6d0705..d9f6bfe72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ packaging = ">=23.2,<26.0" requests = "^2" opentelemetry-api = "^1.33.1" opentelemetry-sdk = "^1.33.1" -opentelemetry-exporter-otlp = "^1.33.1" +opentelemetry-exporter-otlp-proto-http = "^1.33.1" [tool.poetry.group.dev.dependencies] pytest = ">=7.4,<9.0" From 3d8ae6539b4854d28cd02dcb691e961b6de28fb1 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:32:47 +0200 Subject: [PATCH 014/296] chore: release v3.2.5 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 800e5d5a3..bc4b8f3ca 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.4" +__version__ = "3.2.5" diff --git a/pyproject.toml b/pyproject.toml index d9f6bfe72..fe9041d5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.4" +version = "3.2.5" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 06bbd302f8a1abae84ac2f217e2dd251412d104d Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 14 Aug 2025 18:51:20 +0200 Subject: [PATCH 015/296] fix(client): skip string serialization in attribute values (#1297) * fix(client): skip string serialization in attribute values * push --- langfuse/_client/attributes.py | 5 ++++- tests/test_decorators.py | 2 +- tests/test_openai.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py index 1c22b7518..0438b959a 100644 --- a/langfuse/_client/attributes.py +++ b/langfuse/_client/attributes.py @@ -152,7 +152,10 @@ def create_generation_attributes( def _serialize(obj: Any) -> Optional[str]: - return json.dumps(obj, cls=EventSerializer) if obj is not None else None + if obj is None or isinstance(obj, str): + return obj + + return json.dumps(obj, cls=EventSerializer) def _flatten_and_serialize_metadata( diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 6598bac55..fe0a7f4c3 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -905,7 +905,7 @@ async def level_1_function(*args, **kwargs): assert generation.usage.output is not None assert generation.usage.total is not None print(generation) - assert generation.output == "2" + assert generation.output == 2 def test_generator_as_function_input(): diff --git a/tests/test_openai.py b/tests/test_openai.py index 86b0f057c..85205db28 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -119,7 +119,7 @@ def test_openai_chat_completion_stream(openai): assert generation.data[0].usage.input is not None assert generation.data[0].usage.output is not None assert generation.data[0].usage.total is not None - assert generation.data[0].output == "2" + assert generation.data[0].output == 2 assert generation.data[0].completion_start_time is not None # Completion start time for time-to-first-token @@ -179,7 +179,7 @@ def test_openai_chat_completion_stream_with_next_iteration(openai): assert generation.data[0].usage.input is not None assert generation.data[0].usage.output is not None assert generation.data[0].usage.total is not None - assert generation.data[0].output == "2" + assert generation.data[0].output == 2 assert generation.data[0].completion_start_time is not None # Completion start time for time-to-first-token From e28644154c4f4c4e68332e0feb15b272b32ebbf6 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 14 Aug 2025 18:52:05 +0200 Subject: [PATCH 016/296] chore: release v3.2.6 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index bc4b8f3ca..979860fd6 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.5" +__version__ = "3.2.6" diff --git a/pyproject.toml b/pyproject.toml index fe9041d5c..891c5751d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.5" +version = "3.2.6" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 79eb9018e73902ff3c688ebfe621d7331b167c1d Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:10:30 +0200 Subject: [PATCH 017/296] fix(langchain): take only int values in parsed usage (#1299) --- langfuse/langchain/CallbackHandler.py | 51 ++++++++++++++++++--------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 03e73c3f2..7c898807c 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -875,22 +875,41 @@ def _parse_usage_model(usage: typing.Union[pydantic.BaseModel, dict]) -> Any: usage_model = cast(Dict, usage.copy()) # Copy all existing key-value pairs # Skip OpenAI usage types as they are handled server side - if not all( - openai_key in usage_model - for openai_key in ["prompt_tokens", "completion_tokens", "total_tokens"] + if ( + all( + openai_key in usage_model + for openai_key in [ + "prompt_tokens", + "completion_tokens", + "total_tokens", + "prompt_tokens_details", + "completion_tokens_details", + ] + ) + and len(usage_model.keys()) == 5 + ) or ( + all( + openai_key in usage_model + for openai_key in [ + "prompt_tokens", + "completion_tokens", + "total_tokens", + ] + ) + and len(usage_model.keys()) == 3 ): - for model_key, langfuse_key in conversion_list: - if model_key in usage_model: - captured_count = usage_model.pop(model_key) - final_count = ( - sum(captured_count) - if isinstance(captured_count, list) - else captured_count - ) # For Bedrock, the token count is a list when streamed - - usage_model[langfuse_key] = ( - final_count # Translate key and keep the value - ) + return usage_model + + for model_key, langfuse_key in conversion_list: + if model_key in usage_model: + captured_count = usage_model.pop(model_key) + final_count = ( + sum(captured_count) + if isinstance(captured_count, list) + else captured_count + ) # For Bedrock, the token count is a list when streamed + + usage_model[langfuse_key] = final_count # Translate key and keep the value if isinstance(usage_model, dict): if "input_token_details" in usage_model: @@ -965,7 +984,7 @@ def _parse_usage_model(usage: typing.Union[pydantic.BaseModel, dict]) -> Any: if "input" in usage_model: usage_model["input"] = max(0, usage_model["input"] - value) - usage_model = {k: v for k, v in usage_model.items() if not isinstance(v, str)} + usage_model = {k: v for k, v in usage_model.items() if isinstance(v, int)} return usage_model if usage_model else None From 97657c2a1513e256dd25ec026ae35548acb13d4e Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:11:00 +0200 Subject: [PATCH 018/296] chore: release v3.2.7 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 979860fd6..4f4ddbcb3 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.6" +__version__ = "3.2.7" diff --git a/pyproject.toml b/pyproject.toml index 891c5751d..9def258d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.6" +version = "3.2.7" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From c1b83937401df855241487762dd986b0db601d87 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:34:02 +0200 Subject: [PATCH 019/296] fix(openai): handle async response api input and outputs (#1301) --- langfuse/openai.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 96ba14a0c..d8265044b 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -391,7 +391,7 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A if resource.type == "completion": prompt = kwargs.get("prompt", None) - elif resource.object == "Responses": + elif resource.object == "Responses" or resource.object == "AsyncResponses": prompt = kwargs.get("input", None) elif resource.type == "chat": prompt = _extract_chat_prompt(kwargs) @@ -408,6 +408,12 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A else float("inf") ) + parsed_max_completion_tokens = ( + kwargs.get("max_completion_tokens", None) + if not isinstance(kwargs.get("max_completion_tokens", float("inf")), NotGiven) + else None + ) + parsed_top_p = ( kwargs.get("top_p", 1) if not isinstance(kwargs.get("top_p", 1), NotGiven) @@ -441,6 +447,11 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A "frequency_penalty": parsed_frequency_penalty, "presence_penalty": parsed_presence_penalty, } + + if parsed_max_completion_tokens is not None: + modelParameters.pop("max_tokens", None) + modelParameters["max_completion_tokens"] = parsed_max_completion_tokens + if parsed_n is not None and parsed_n > 1: modelParameters["n"] = parsed_n @@ -672,7 +683,7 @@ def _get_langfuse_data_from_default_response( completion = choice.text if _is_openai_v1() else choice.get("text", None) - elif resource.object == "Responses": + elif resource.object == "Responses" or resource.object == "AsyncResponses": output = response.get("output", {}) if not isinstance(output, list): @@ -922,6 +933,7 @@ def _finalize(self) -> None: model, completion, usage, metadata = ( _extract_streamed_response_api_response(self.items) if self.resource.object == "Responses" + or self.resource.object == "AsyncResponses" else _extract_streamed_openai_response(self.resource, self.items) ) @@ -992,6 +1004,7 @@ async def _finalize(self) -> None: model, completion, usage, metadata = ( _extract_streamed_response_api_response(self.items) if self.resource.object == "Responses" + or self.resource.object == "AsyncResponses" else _extract_streamed_openai_response(self.resource, self.items) ) From 117f6ee0686d9345a94ec97c4217450abcb3f671 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:34:48 +0200 Subject: [PATCH 020/296] chore: release v3.2.8 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 4f4ddbcb3..feb3aa763 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.7" +__version__ = "3.2.8" diff --git a/pyproject.toml b/pyproject.toml index 9def258d6..3937fac5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.7" +version = "3.2.8" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From da1eb860e8ec0646ad7fbceb7286d333b3fb9001 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:00:59 +0200 Subject: [PATCH 021/296] feat(langchain): add update_trace argument (#1302) --- langfuse/langchain/CallbackHandler.py | 36 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 7c898807c..ba2460c47 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -56,7 +56,15 @@ class LangchainCallbackHandler(LangchainBaseCallbackHandler): - def __init__(self, *, public_key: Optional[str] = None) -> None: + def __init__( + self, *, public_key: Optional[str] = None, update_trace: bool = False + ) -> None: + """Initialize the LangchainCallbackHandler. + + Args: + public_key: Optional Langfuse public key. If not provided, will use the default client configuration. + update_trace: Whether to update the Langfuse trace with the chains input / output / metadata / name. Defaults to False. + """ self.client = get_client(public_key=public_key) self.runs: Dict[UUID, Union[LangfuseSpan, LangfuseGeneration]] = {} @@ -64,6 +72,7 @@ def __init__(self, *, public_key: Optional[str] = None) -> None: self.updated_completion_start_time_memo: Set[UUID] = set() self.last_trace_id: Optional[str] = None + self.update_trace = update_trace def on_llm_new_token( self, @@ -207,7 +216,19 @@ def on_chain_start( ), ) span.update_trace( - **self._parse_langfuse_trace_attributes_from_metadata(metadata) + **( + cast( + Any, + { + "input": inputs, + "name": span_name, + "metadata": span_metadata, + }, + ) + if self.update_trace + else {} + ), + **self._parse_langfuse_trace_attributes_from_metadata(metadata), ) self.runs[run_id] = span else: @@ -322,14 +343,21 @@ def on_chain_end( if run_id not in self.runs: raise Exception("run not found") - self.runs[run_id].update( + span = self.runs[run_id] + span.update( output=outputs, input=kwargs.get("inputs"), - ).end() + ) + + if parent_run_id is None and self.update_trace: + span.update_trace(output=outputs, input=kwargs.get("inputs")) + + span.end() del self.runs[run_id] self._deregister_langfuse_prompt(run_id) + except Exception as e: langfuse_logger.exception(e) From 342efe9fc2368860ec0a0bccef16d40a6454bb46 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:01:28 +0200 Subject: [PATCH 022/296] chore: release v3.3.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index feb3aa763..257d59df6 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.2.8" +__version__ = "3.3.0" diff --git a/pyproject.toml b/pyproject.toml index 3937fac5c..ece5e120e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.2.8" +version = "3.3.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 1596dddd534b02f16f09d0ef4dd76bf5c7a13329 Mon Sep 17 00:00:00 2001 From: Nimar Date: Fri, 22 Aug 2025 14:19:41 +0200 Subject: [PATCH 023/296] feat(traces): add more observation types (#1290) * init commit * Use as_type * clean * cleanup * cleanup * todo clarifty case sensitivity * format * revert * add test * lower case * fix lint * format * types * cleanup * fix test * update * update * fix cases * fix some more types * update SDK * fix types * add type checing isntructions * restore * restore 2 * restore * restore 3 * simplify * simplift * simplify * fix case * rearrange spans * restrucure a bit * cleanup * make mypy happy * py3.9 compat * happy mypy * cleanup * a bit more clean * cleanup * overload all the things * overload * rename spanwrapper to observation wrapper * don't update events * fix test * fix span * fix test * langchain * formatting * add langchain test * add core test * cleanup * add deprecation warning * fix types * change generation like and span like * fix * test format * from review * format * add generation-like test * chore: update auto gen SDK (#1300) * unrelated changes to generated sdk * also readme * reset * revert * revert * fix lint * fix tests * embedding correct attrs * fix typing * fix bug * fix mypy --- CONTRIBUTING.md | 7 + langfuse/__init__.py | 22 +- langfuse/_client/attributes.py | 13 +- langfuse/_client/client.py | 1129 ++++++++++++---- langfuse/_client/constants.py | 57 + langfuse/_client/observe.py | 124 +- langfuse/_client/span.py | 1177 ++++++++++++----- langfuse/api/README.md | 28 +- langfuse/api/__init__.py | 18 + langfuse/api/client.py | 8 + langfuse/api/reference.md | 423 ++++++ langfuse/api/resources/__init__.py | 20 + .../resources/annotation_queues/__init__.py | 8 + .../api/resources/annotation_queues/client.py | 494 +++++++ .../annotation_queues/types/__init__.py | 12 + .../annotation_queue_assignment_request.py | 44 + ...te_annotation_queue_assignment_response.py | 46 + .../types/create_annotation_queue_request.py | 46 + ...te_annotation_queue_assignment_response.py | 42 + .../api/resources/commons/types/map_value.py | 1 - .../ingestion/types/observation_type.py | 28 + .../api/resources/llm_connections/__init__.py | 15 + .../api/resources/llm_connections/client.py | 340 +++++ .../llm_connections/types/__init__.py | 13 + .../llm_connections/types/llm_adapter.py | 37 + .../llm_connections/types/llm_connection.py | 85 ++ .../types/paginated_llm_connections.py | 45 + .../types/upsert_llm_connection_request.py | 88 ++ langfuse/api/resources/observations/client.py | 11 + langfuse/langchain/CallbackHandler.py | 144 +- langfuse/openai.py | 6 +- tests/test_core_sdk.py | 142 ++ tests/test_datasets.py | 2 +- tests/test_deprecation.py | 119 ++ tests/test_langchain.py | 107 +- tests/test_otel.py | 209 ++- 36 files changed, 4474 insertions(+), 636 deletions(-) create mode 100644 langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py create mode 100644 langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py create mode 100644 langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py create mode 100644 langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py create mode 100644 langfuse/api/resources/llm_connections/__init__.py create mode 100644 langfuse/api/resources/llm_connections/client.py create mode 100644 langfuse/api/resources/llm_connections/types/__init__.py create mode 100644 langfuse/api/resources/llm_connections/types/llm_adapter.py create mode 100644 langfuse/api/resources/llm_connections/types/llm_connection.py create mode 100644 langfuse/api/resources/llm_connections/types/paginated_llm_connections.py create mode 100644 langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py create mode 100644 tests/test_deprecation.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62b490f33..1d2bfd294 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,13 @@ poetry install --all-extras poetry run pre-commit install ``` +### Type Checking + +To run type checking on the langfuse package, run: +```sh +poetry run mypy langfuse --no-error-summary +``` + ### Tests #### Setup diff --git a/langfuse/__init__.py b/langfuse/__init__.py index 5d8da4cc2..3449e851f 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -2,9 +2,21 @@ from ._client import client as _client_module from ._client.attributes import LangfuseOtelSpanAttributes +from ._client.constants import ObservationTypeLiteral from ._client.get_client import get_client from ._client.observe import observe -from ._client.span import LangfuseEvent, LangfuseGeneration, LangfuseSpan +from ._client.span import ( + LangfuseEvent, + LangfuseGeneration, + LangfuseSpan, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseEmbedding, + LangfuseEvaluator, + LangfuseRetriever, + LangfuseGuardrail, +) Langfuse = _client_module.Langfuse @@ -12,8 +24,16 @@ "Langfuse", "get_client", "observe", + "ObservationTypeLiteral", "LangfuseSpan", "LangfuseGeneration", "LangfuseEvent", "LangfuseOtelSpanAttributes", + "LangfuseAgent", + "LangfuseTool", + "LangfuseChain", + "LangfuseEmbedding", + "LangfuseEvaluator", + "LangfuseRetriever", + "LangfuseGuardrail", ] diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py index 0438b959a..5ae81000c 100644 --- a/langfuse/_client/attributes.py +++ b/langfuse/_client/attributes.py @@ -14,6 +14,11 @@ from datetime import datetime from typing import Any, Dict, List, Literal, Optional, Union +from langfuse._client.constants import ( + ObservationTypeGenerationLike, + ObservationTypeSpanLike, +) + from langfuse._utils.serializer import EventSerializer from langfuse.model import PromptClient from langfuse.types import MapValue, SpanLevel @@ -93,9 +98,12 @@ def create_span_attributes( level: Optional[SpanLevel] = None, status_message: Optional[str] = None, version: Optional[str] = None, + observation_type: Optional[ + Union[ObservationTypeSpanLike, Literal["event"]] + ] = "span", ) -> dict: attributes = { - LangfuseOtelSpanAttributes.OBSERVATION_TYPE: "span", + LangfuseOtelSpanAttributes.OBSERVATION_TYPE: observation_type, LangfuseOtelSpanAttributes.OBSERVATION_LEVEL: level, LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE: status_message, LangfuseOtelSpanAttributes.VERSION: version, @@ -122,9 +130,10 @@ def create_generation_attributes( usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, + observation_type: Optional[ObservationTypeGenerationLike] = "generation", ) -> dict: attributes = { - LangfuseOtelSpanAttributes.OBSERVATION_TYPE: "generation", + LangfuseOtelSpanAttributes.OBSERVATION_TYPE: observation_type, LangfuseOtelSpanAttributes.OBSERVATION_LEVEL: level, LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE: status_message, LangfuseOtelSpanAttributes.VERSION: version, diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 6aba529fc..18eca4bfd 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -5,12 +5,23 @@ import logging import os +import warnings import re import urllib.parse from datetime import datetime from hashlib import sha256 from time import time_ns -from typing import Any, Dict, List, Literal, Optional, Union, cast, overload +from typing import ( + Any, + Dict, + List, + Literal, + Optional, + Union, + Type, + cast, + overload, +) import backoff import httpx @@ -36,11 +47,25 @@ LANGFUSE_TRACING_ENABLED, LANGFUSE_TRACING_ENVIRONMENT, ) +from langfuse._client.constants import ( + ObservationTypeLiteral, + ObservationTypeLiteralNoEvent, + ObservationTypeGenerationLike, + ObservationTypeSpanLike, + get_observation_types_list, +) from langfuse._client.resource_manager import LangfuseResourceManager from langfuse._client.span import ( LangfuseEvent, LangfuseGeneration, LangfuseSpan, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, ) from langfuse._utils import _get_timestamp from langfuse._utils.parse_error import handle_fern_exception @@ -297,39 +322,10 @@ def start_span( span.end() ``` """ - if trace_context: - trace_id = trace_context.get("trace_id", None) - parent_span_id = trace_context.get("parent_span_id", None) - - if trace_id: - remote_parent_span = self._create_remote_parent_span( - trace_id=trace_id, parent_span_id=parent_span_id - ) - - with otel_trace_api.use_span( - cast(otel_trace_api.Span, remote_parent_span) - ): - otel_span = self._otel_tracer.start_span(name=name) - otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True) - - return LangfuseSpan( - otel_span=otel_span, - langfuse_client=self, - environment=self._environment, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ) - - otel_span = self._otel_tracer.start_span(name=name) - - return LangfuseSpan( - otel_span=otel_span, - langfuse_client=self, - environment=self._environment, + return self.start_observation( + trace_context=trace_context, + name=name, + as_type="span", input=input, output=output, metadata=metadata, @@ -386,52 +382,137 @@ def start_as_current_span( child_span.update(output="sub-result") ``` """ - if trace_context: - trace_id = trace_context.get("trace_id", None) - parent_span_id = trace_context.get("parent_span_id", None) + return self.start_as_current_observation( + trace_context=trace_context, + name=name, + as_type="span", + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + end_on_exit=end_on_exit, + ) - if trace_id: - remote_parent_span = self._create_remote_parent_span( - trace_id=trace_id, parent_span_id=parent_span_id - ) + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["generation"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> LangfuseGeneration: ... - return cast( - _AgnosticContextManager[LangfuseSpan], - self._create_span_with_parent_context( - as_type="span", - name=name, - remote_parent_span=remote_parent_span, - parent=None, - end_on_exit=end_on_exit, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ), - ) + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["span"] = "span", + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseSpan: ... - return cast( - _AgnosticContextManager[LangfuseSpan], - self._start_as_current_otel_span_with_processed_media( - as_type="span", - name=name, - end_on_exit=end_on_exit, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ), - ) + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["agent"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseAgent: ... - def start_generation( + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["tool"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseTool: ... + + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["chain"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseChain: ... + + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["retriever"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseRetriever: ... + + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["evaluator"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseEvaluator: ... + + @overload + def start_observation( self, *, trace_context: Optional[TraceContext] = None, name: str, + as_type: Literal["embedding"], input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -444,56 +525,76 @@ def start_generation( usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, - ) -> LangfuseGeneration: - """Create a new generation span for model generations. + ) -> LangfuseEmbedding: ... - This method creates a specialized span for tracking model generations. - It includes additional fields specific to model generations such as model name, - token usage, and cost details. + @overload + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["guardrail"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> LangfuseGuardrail: ... - The created generation span will be the child of the current span in the context. + def start_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: ObservationTypeLiteralNoEvent = "span", + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ]: + """Create a new observation of the specified type. + + This method creates a new observation but does not set it as the current span in the + context. To create and use an observation within a context, use start_as_current_observation(). Args: trace_context: Optional context for connecting to an existing trace - name: Name of the generation operation - input: Input data for the model (e.g., prompts) - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management + name: Name of the observation + as_type: Type of observation to create (defaults to "span") + input: Input data for the operation + output: Output data from the operation + metadata: Additional metadata to associate with the observation + version: Version identifier for the code or component + level: Importance level of the observation + status_message: Optional status message for the observation + completion_start_time: When the model started generating (for generation types) + model: Name/identifier of the AI model used (for generation types) + model_parameters: Parameters used for the model (for generation types) + usage_details: Token usage information (for generation types) + cost_details: Cost information (for generation types) + prompt: Associated prompt template (for generation types) Returns: - A LangfuseGeneration object that must be ended with .end() when complete - - Example: - ```python - generation = langfuse.start_generation( - name="answer-generation", - model="gpt-4", - input={"prompt": "Explain quantum computing"}, - model_parameters={"temperature": 0.7} - ) - try: - # Call model API - response = llm.generate(...) - - generation.update( - output=response.text, - usage_details={ - "prompt_tokens": response.usage.prompt_tokens, - "completion_tokens": response.usage.completion_tokens - } - ) - finally: - generation.end() - ``` + An observation object of the appropriate type that must be ended with .end() """ if trace_context: trace_id = trace_context.get("trace_id", None) @@ -510,9 +611,9 @@ def start_generation( otel_span = self._otel_tracer.start_span(name=name) otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True) - return LangfuseGeneration( + return self._create_observation_from_otel_span( otel_span=otel_span, - langfuse_client=self, + as_type=as_type, input=input, output=output, metadata=metadata, @@ -529,9 +630,9 @@ def start_generation( otel_span = self._otel_tracer.start_span(name=name) - return LangfuseGeneration( + return self._create_observation_from_otel_span( otel_span=otel_span, - langfuse_client=self, + as_type=as_type, input=input, output=output, metadata=metadata, @@ -546,11 +647,11 @@ def start_generation( prompt=prompt, ) - def start_as_current_generation( + def _create_observation_from_otel_span( self, *, - trace_context: Optional[TraceContext] = None, - name: str, + otel_span: otel_trace_api.Span, + as_type: ObservationTypeLiteralNoEvent, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -563,34 +664,202 @@ def start_as_current_generation( usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, - end_on_exit: Optional[bool] = None, - ) -> _AgnosticContextManager[LangfuseGeneration]: - """Create a new generation span and set it as the current span in a context manager. - - This method creates a specialized span for model generations and sets it as the - current span within a context manager. Use this method with a 'with' statement to - automatically handle the generation span lifecycle within a code block. - - The created generation span will be the child of the current span in the context. - - Args: - trace_context: Optional context for connecting to an existing trace - name: Name of the generation operation - input: Input data for the model (e.g., prompts) - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management - end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks. - - Returns: + ) -> Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ]: + """Create the appropriate observation type from an OTEL span.""" + if as_type in get_observation_types_list(ObservationTypeGenerationLike): + observation_class = self._get_span_class(as_type) + # Type ignore to prevent overloads of internal _get_span_class function, + # issue is that LangfuseEvent could be returned and that classes have diff. args + return observation_class( # type: ignore[return-value,call-arg] + otel_span=otel_span, + langfuse_client=self, + environment=self._environment, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ) + else: + # For other types (e.g. span, guardrail), create appropriate class without generation properties + observation_class = self._get_span_class(as_type) + # Type ignore to prevent overloads of internal _get_span_class function, + # issue is that LangfuseEvent could be returned and that classes have diff. args + return observation_class( # type: ignore[return-value,call-arg] + otel_span=otel_span, + langfuse_client=self, + environment=self._environment, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + ) + # span._observation_type = as_type + # span._otel_span.set_attribute("langfuse.observation.type", as_type) + # return span + + def start_generation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> LangfuseGeneration: + """[DEPRECATED] Create a new generation span for model generations. + + DEPRECATED: This method is deprecated and will be removed in a future version. + Use start_observation(as_type='generation') instead. + + This method creates a specialized span for tracking model generations. + It includes additional fields specific to model generations such as model name, + token usage, and cost details. + + The created generation span will be the child of the current span in the context. + + Args: + trace_context: Optional context for connecting to an existing trace + name: Name of the generation operation + input: Input data for the model (e.g., prompts) + output: Output from the model (e.g., completions) + metadata: Additional metadata to associate with the generation + version: Version identifier for the model or component + level: Importance level of the generation (info, warning, error) + status_message: Optional status message for the generation + completion_start_time: When the model started generating the response + model: Name/identifier of the AI model used (e.g., "gpt-4") + model_parameters: Parameters used for the model (e.g., temperature, max_tokens) + usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) + cost_details: Cost information for the model call + prompt: Associated prompt template from Langfuse prompt management + + Returns: + A LangfuseGeneration object that must be ended with .end() when complete + + Example: + ```python + generation = langfuse.start_generation( + name="answer-generation", + model="gpt-4", + input={"prompt": "Explain quantum computing"}, + model_parameters={"temperature": 0.7} + ) + try: + # Call model API + response = llm.generate(...) + + generation.update( + output=response.text, + usage_details={ + "prompt_tokens": response.usage.prompt_tokens, + "completion_tokens": response.usage.completion_tokens + } + ) + finally: + generation.end() + ``` + """ + warnings.warn( + "start_generation is deprecated and will be removed in a future version. " + "Use start_observation(as_type='generation') instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.start_observation( + trace_context=trace_context, + name=name, + as_type="generation", + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ) + + def start_as_current_generation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseGeneration]: + """[DEPRECATED] Create a new generation span and set it as the current span in a context manager. + + DEPRECATED: This method is deprecated and will be removed in a future version. + Use start_as_current_observation(as_type='generation') instead. + + This method creates a specialized span for model generations and sets it as the + current span within a context manager. Use this method with a 'with' statement to + automatically handle the generation span lifecycle within a code block. + + The created generation span will be the child of the current span in the context. + + Args: + trace_context: Optional context for connecting to an existing trace + name: Name of the generation operation + input: Input data for the model (e.g., prompts) + output: Output from the model (e.g., completions) + metadata: Additional metadata to associate with the generation + version: Version identifier for the model or component + level: Importance level of the generation (info, warning, error) + status_message: Optional status message for the generation + completion_start_time: When the model started generating the response + model: Name/identifier of the AI model used (e.g., "gpt-4") + model_parameters: Parameters used for the model (e.g., temperature, max_tokens) + usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) + cost_details: Cost information for the model call + prompt: Associated prompt template from Langfuse prompt management + end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks. + + Returns: A context manager that yields a LangfuseGeneration Example: @@ -613,58 +882,452 @@ def start_as_current_generation( ) ``` """ - if trace_context: - trace_id = trace_context.get("trace_id", None) - parent_span_id = trace_context.get("parent_span_id", None) + warnings.warn( + "start_as_current_generation is deprecated and will be removed in a future version. " + "Use start_as_current_observation(as_type='generation') instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.start_as_current_observation( + trace_context=trace_context, + name=name, + as_type="generation", + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + end_on_exit=end_on_exit, + ) - if trace_id: - remote_parent_span = self._create_remote_parent_span( - trace_id=trace_id, parent_span_id=parent_span_id - ) + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["generation"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseGeneration]: ... - return cast( - _AgnosticContextManager[LangfuseGeneration], - self._create_span_with_parent_context( - as_type="generation", - name=name, - remote_parent_span=remote_parent_span, - parent=None, - end_on_exit=end_on_exit, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ), - ) + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["span"] = "span", + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseSpan]: ... - return cast( - _AgnosticContextManager[LangfuseGeneration], - self._start_as_current_otel_span_with_processed_media( + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["agent"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseAgent]: ... + + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["tool"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseTool]: ... + + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["chain"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseChain]: ... + + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["retriever"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseRetriever]: ... + + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["evaluator"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseEvaluator]: ... + + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["embedding"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseEmbedding]: ... + + @overload + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: Literal["guardrail"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + end_on_exit: Optional[bool] = None, + ) -> _AgnosticContextManager[LangfuseGuardrail]: ... + + def start_as_current_observation( + self, + *, + trace_context: Optional[TraceContext] = None, + name: str, + as_type: ObservationTypeLiteralNoEvent = "span", + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + end_on_exit: Optional[bool] = None, + ) -> Union[ + _AgnosticContextManager[LangfuseGeneration], + _AgnosticContextManager[LangfuseSpan], + _AgnosticContextManager[LangfuseAgent], + _AgnosticContextManager[LangfuseTool], + _AgnosticContextManager[LangfuseChain], + _AgnosticContextManager[LangfuseRetriever], + _AgnosticContextManager[LangfuseEvaluator], + _AgnosticContextManager[LangfuseEmbedding], + _AgnosticContextManager[LangfuseGuardrail], + ]: + """Create a new observation and set it as the current span in a context manager. + + This method creates a new observation of the specified type and sets it as the + current span within a context manager. Use this method with a 'with' statement to + automatically handle the observation lifecycle within a code block. + + The created observation will be the child of the current span in the context. + + Args: + trace_context: Optional context for connecting to an existing trace + name: Name of the observation (e.g., function or operation name) + as_type: Type of observation to create (defaults to "span") + input: Input data for the operation (can be any JSON-serializable object) + output: Output data from the operation (can be any JSON-serializable object) + metadata: Additional metadata to associate with the observation + version: Version identifier for the code or component + level: Importance level of the observation (info, warning, error) + status_message: Optional status message for the observation + end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks. + + The following parameters are available when as_type is: "generation" or "embedding". + completion_start_time: When the model started generating the response + model: Name/identifier of the AI model used (e.g., "gpt-4") + model_parameters: Parameters used for the model (e.g., temperature, max_tokens) + usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) + cost_details: Cost information for the model call + prompt: Associated prompt template from Langfuse prompt management + + Returns: + A context manager that yields the appropriate observation type based on as_type + + Example: + ```python + # Create a span + with langfuse.start_as_current_observation(name="process-query", as_type="span") as span: + # Do work + result = process_data() + span.update(output=result) + + # Create a child span automatically + with span.start_as_current_span(name="sub-operation") as child_span: + # Do sub-operation work + child_span.update(output="sub-result") + + # Create a tool observation + with langfuse.start_as_current_observation(name="web-search", as_type="tool") as tool: + # Do tool work + results = search_web(query) + tool.update(output=results) + + # Create a generation observation + with langfuse.start_as_current_observation( + name="answer-generation", as_type="generation", - name=name, - end_on_exit=end_on_exit, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ), + model="gpt-4" + ) as generation: + # Generate answer + response = llm.generate(...) + generation.update(output=response) + ``` + """ + if as_type in get_observation_types_list(ObservationTypeGenerationLike): + if trace_context: + trace_id = trace_context.get("trace_id", None) + parent_span_id = trace_context.get("parent_span_id", None) + + if trace_id: + remote_parent_span = self._create_remote_parent_span( + trace_id=trace_id, parent_span_id=parent_span_id + ) + + return cast( + Union[ + _AgnosticContextManager[LangfuseGeneration], + _AgnosticContextManager[LangfuseEmbedding], + ], + self._create_span_with_parent_context( + as_type=as_type, + name=name, + remote_parent_span=remote_parent_span, + parent=None, + end_on_exit=end_on_exit, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ), + ) + + return cast( + Union[ + _AgnosticContextManager[LangfuseGeneration], + _AgnosticContextManager[LangfuseEmbedding], + ], + self._start_as_current_otel_span_with_processed_media( + as_type=as_type, + name=name, + end_on_exit=end_on_exit, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ), + ) + + if as_type in get_observation_types_list(ObservationTypeSpanLike): + if trace_context: + trace_id = trace_context.get("trace_id", None) + parent_span_id = trace_context.get("parent_span_id", None) + + if trace_id: + remote_parent_span = self._create_remote_parent_span( + trace_id=trace_id, parent_span_id=parent_span_id + ) + + return cast( + Union[ + _AgnosticContextManager[LangfuseSpan], + _AgnosticContextManager[LangfuseAgent], + _AgnosticContextManager[LangfuseTool], + _AgnosticContextManager[LangfuseChain], + _AgnosticContextManager[LangfuseRetriever], + _AgnosticContextManager[LangfuseEvaluator], + _AgnosticContextManager[LangfuseGuardrail], + ], + self._create_span_with_parent_context( + as_type=as_type, + name=name, + remote_parent_span=remote_parent_span, + parent=None, + end_on_exit=end_on_exit, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + ), + ) + + return cast( + Union[ + _AgnosticContextManager[LangfuseSpan], + _AgnosticContextManager[LangfuseAgent], + _AgnosticContextManager[LangfuseTool], + _AgnosticContextManager[LangfuseChain], + _AgnosticContextManager[LangfuseRetriever], + _AgnosticContextManager[LangfuseEvaluator], + _AgnosticContextManager[LangfuseGuardrail], + ], + self._start_as_current_otel_span_with_processed_media( + as_type=as_type, + name=name, + end_on_exit=end_on_exit, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + ), + ) + + # This should never be reached since all valid types are handled above + langfuse_logger.warning( + f"Unknown observation type: {as_type}, falling back to span" ) + return self._start_as_current_otel_span_with_processed_media( + as_type="span", + name=name, + end_on_exit=end_on_exit, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + ) + + def _get_span_class( + self, + as_type: ObservationTypeLiteral, + ) -> Union[ + Type[LangfuseAgent], + Type[LangfuseTool], + Type[LangfuseChain], + Type[LangfuseRetriever], + Type[LangfuseEvaluator], + Type[LangfuseEmbedding], + Type[LangfuseGuardrail], + Type[LangfuseGeneration], + Type[LangfuseEvent], + Type[LangfuseSpan], + ]: + """Get the appropriate span class based on as_type.""" + normalized_type = as_type.lower() + + if normalized_type == "agent": + return LangfuseAgent + elif normalized_type == "tool": + return LangfuseTool + elif normalized_type == "chain": + return LangfuseChain + elif normalized_type == "retriever": + return LangfuseRetriever + elif normalized_type == "evaluator": + return LangfuseEvaluator + elif normalized_type == "embedding": + return LangfuseEmbedding + elif normalized_type == "guardrail": + return LangfuseGuardrail + elif normalized_type == "generation": + return LangfuseGeneration + elif normalized_type == "event": + return LangfuseEvent + elif normalized_type == "span": + return LangfuseSpan + else: + return LangfuseSpan @_agnosticcontextmanager def _create_span_with_parent_context( @@ -673,7 +1336,7 @@ def _create_span_with_parent_context( name: str, parent: Optional[otel_trace_api.Span] = None, remote_parent_span: Optional[otel_trace_api.Span] = None, - as_type: Literal["generation", "span"], + as_type: ObservationTypeLiteralNoEvent, end_on_exit: Optional[bool] = None, input: Optional[Any] = None, output: Optional[Any] = None, @@ -720,7 +1383,7 @@ def _start_as_current_otel_span_with_processed_media( self, *, name: str, - as_type: Optional[Literal["generation", "span"]] = None, + as_type: Optional[ObservationTypeLiteralNoEvent] = None, end_on_exit: Optional[bool] = None, input: Optional[Any] = None, output: Optional[Any] = None, @@ -739,37 +1402,38 @@ def _start_as_current_otel_span_with_processed_media( name=name, end_on_exit=end_on_exit if end_on_exit is not None else True, ) as otel_span: - yield ( - LangfuseSpan( - otel_span=otel_span, - langfuse_client=self, - environment=self._environment, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ) - if as_type == "span" - else LangfuseGeneration( - otel_span=otel_span, - langfuse_client=self, - environment=self._environment, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, + span_class = self._get_span_class( + as_type or "generation" + ) # default was "generation" + common_args = { + "otel_span": otel_span, + "langfuse_client": self, + "environment": self._environment, + "input": input, + "output": output, + "metadata": metadata, + "version": version, + "level": level, + "status_message": status_message, + } + + if span_class in [ + LangfuseGeneration, + LangfuseEmbedding, + ]: + common_args.update( + { + "completion_start_time": completion_start_time, + "model": model, + "model_parameters": model_parameters, + "usage_details": usage_details, + "cost_details": cost_details, + "prompt": prompt, + } ) - ) + # For span-like types (span, agent, tool, chain, retriever, evaluator, guardrail), no generation properties needed + + yield span_class(**common_args) # type: ignore[arg-type] def _get_current_otel_span(self) -> Optional[otel_trace_api.Span]: current_span = otel_trace_api.get_current_span() @@ -996,7 +1660,12 @@ def update_current_trace( current_otel_span = self._get_current_otel_span() if current_otel_span is not None: - span = LangfuseSpan( + existing_observation_type = current_otel_span.attributes.get( # type: ignore[attr-defined] + LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "span" + ) + # We need to preserve the class to keep the corret observation type + span_class = self._get_span_class(existing_observation_type) + span = span_class( otel_span=current_otel_span, langfuse_client=self, environment=self._environment, diff --git a/langfuse/_client/constants.py b/langfuse/_client/constants.py index 1c805ddc3..b699480c0 100644 --- a/langfuse/_client/constants.py +++ b/langfuse/_client/constants.py @@ -3,4 +3,61 @@ This module defines constants used throughout the Langfuse OpenTelemetry integration. """ +from typing import Literal, List, get_args, Union, Any +from typing_extensions import TypeAlias + LANGFUSE_TRACER_NAME = "langfuse-sdk" + + +"""Note: this type is used with .__args__ / get_args in some cases and therefore must remain flat""" +ObservationTypeGenerationLike: TypeAlias = Literal[ + "generation", + "embedding", +] + +ObservationTypeSpanLike: TypeAlias = Literal[ + "span", + "agent", + "tool", + "chain", + "retriever", + "evaluator", + "guardrail", +] + +ObservationTypeLiteralNoEvent: TypeAlias = Union[ + ObservationTypeGenerationLike, + ObservationTypeSpanLike, +] + +"""Enumeration of valid observation types for Langfuse tracing. + +This Literal defines all available observation types that can be used with the @observe +decorator and other Langfuse SDK methods. +""" +ObservationTypeLiteral: TypeAlias = Union[ + ObservationTypeLiteralNoEvent, Literal["event"] +] + + +def get_observation_types_list( + literal_type: Any, +) -> List[str]: + """Flattens the Literal type to provide a list of strings. + + Args: + literal_type: A Literal type, TypeAlias, or union of Literals to flatten + + Returns: + Flat list of all string values contained in the Literal type + """ + result = [] + args = get_args(literal_type) + + for arg in args: + if hasattr(arg, "__args__"): + result.extend(get_observation_types_list(arg)) + else: + result.append(arg) + + return result diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index 0fef2b5dd..ce848e04a 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -10,7 +10,6 @@ Dict, Generator, Iterable, - Literal, Optional, Tuple, TypeVar, @@ -25,8 +24,23 @@ from langfuse._client.environment_variables import ( LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, ) + +from langfuse._client.constants import ( + ObservationTypeLiteralNoEvent, + get_observation_types_list, +) from langfuse._client.get_client import _set_current_public_key, get_client -from langfuse._client.span import LangfuseGeneration, LangfuseSpan +from langfuse._client.span import ( + LangfuseGeneration, + LangfuseSpan, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, +) from langfuse.types import TraceContext F = TypeVar("F", bound=Callable[..., Any]) @@ -65,7 +79,7 @@ def observe( func: None = None, *, name: Optional[str] = None, - as_type: Optional[Literal["generation"]] = None, + as_type: Optional[ObservationTypeLiteralNoEvent] = None, capture_input: Optional[bool] = None, capture_output: Optional[bool] = None, transform_to_string: Optional[Callable[[Iterable], str]] = None, @@ -76,7 +90,7 @@ def observe( func: Optional[F] = None, *, name: Optional[str] = None, - as_type: Optional[Literal["generation"]] = None, + as_type: Optional[ObservationTypeLiteralNoEvent] = None, capture_input: Optional[bool] = None, capture_output: Optional[bool] = None, transform_to_string: Optional[Callable[[Iterable], str]] = None, @@ -93,8 +107,11 @@ def observe( Args: func (Optional[Callable]): The function to decorate. When used with parentheses @observe(), this will be None. name (Optional[str]): Custom name for the created trace or span. If not provided, the function name is used. - as_type (Optional[Literal["generation"]]): Set to "generation" to create a specialized LLM generation span - with model metrics support, suitable for tracking language model outputs. + as_type (Optional[Literal]): Set the observation type. Supported values: + "generation", "span", "agent", "tool", "chain", "retriever", "embedding", "evaluator", "guardrail". + Observation types are highlighted in the Langfuse UI for filtering and visualization. + The types "generation" and "embedding" create a span on which additional attributes such as model metrics + can be set. Returns: Callable: A wrapped version of the original function that automatically creates and manages Langfuse spans. @@ -146,6 +163,13 @@ def sub_process(): - For async functions, the decorator returns an async function wrapper. - For sync functions, the decorator returns a synchronous wrapper. """ + valid_types = set(get_observation_types_list(ObservationTypeLiteralNoEvent)) + if as_type is not None and as_type not in valid_types: + self._log.warning( + f"Invalid as_type '{as_type}'. Valid types are: {', '.join(sorted(valid_types))}. Defaulting to 'span'." + ) + as_type = "span" + function_io_capture_enabled = os.environ.get( LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, "True" ).lower() not in ("false", "0") @@ -182,13 +206,13 @@ def decorator(func: F) -> F: ) """Handle decorator with or without parentheses. - + This logic enables the decorator to work both with and without parentheses: - @observe - Python passes the function directly to the decorator - @observe() - Python calls the decorator first, which must return a function decorator - + When called without arguments (@observe), the func parameter contains the function to decorate, - so we directly apply the decorator to it. When called with parentheses (@observe()), + so we directly apply the decorator to it. When called with parentheses (@observe()), func is None, so we return the decorator function itself for Python to apply in the next step. """ if func is None: @@ -201,7 +225,7 @@ def _async_observe( func: F, *, name: Optional[str], - as_type: Optional[Literal["generation"]], + as_type: Optional[ObservationTypeLiteralNoEvent], capture_input: bool, capture_output: bool, transform_to_string: Optional[Callable[[Iterable], str]] = None, @@ -239,22 +263,21 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: Union[ _AgnosticContextManager[LangfuseGeneration], _AgnosticContextManager[LangfuseSpan], + _AgnosticContextManager[LangfuseAgent], + _AgnosticContextManager[LangfuseTool], + _AgnosticContextManager[LangfuseChain], + _AgnosticContextManager[LangfuseRetriever], + _AgnosticContextManager[LangfuseEvaluator], + _AgnosticContextManager[LangfuseEmbedding], + _AgnosticContextManager[LangfuseGuardrail], ] ] = ( - ( - langfuse_client.start_as_current_generation( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early - ) - if as_type == "generation" - else langfuse_client.start_as_current_span( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early - ) + langfuse_client.start_as_current_observation( + name=final_name, + as_type=as_type or "span", + trace_context=trace_context, + input=input, + end_on_exit=False, # when returning a generator, closing on exit would be to early ) if langfuse_client else None @@ -308,7 +331,7 @@ def _sync_observe( func: F, *, name: Optional[str], - as_type: Optional[Literal["generation"]], + as_type: Optional[ObservationTypeLiteralNoEvent], capture_input: bool, capture_output: bool, transform_to_string: Optional[Callable[[Iterable], str]] = None, @@ -344,22 +367,21 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: Union[ _AgnosticContextManager[LangfuseGeneration], _AgnosticContextManager[LangfuseSpan], + _AgnosticContextManager[LangfuseAgent], + _AgnosticContextManager[LangfuseTool], + _AgnosticContextManager[LangfuseChain], + _AgnosticContextManager[LangfuseRetriever], + _AgnosticContextManager[LangfuseEvaluator], + _AgnosticContextManager[LangfuseEmbedding], + _AgnosticContextManager[LangfuseGuardrail], ] ] = ( - ( - langfuse_client.start_as_current_generation( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early - ) - if as_type == "generation" - else langfuse_client.start_as_current_span( - name=final_name, - trace_context=trace_context, - input=input, - end_on_exit=False, # when returning a generator, closing on exit would be to early - ) + langfuse_client.start_as_current_observation( + name=final_name, + as_type=as_type or "span", + trace_context=trace_context, + input=input, + end_on_exit=False, # when returning a generator, closing on exit would be to early ) if langfuse_client else None @@ -432,7 +454,17 @@ def _get_input_from_func_args( def _wrap_sync_generator_result( self, - langfuse_span_or_generation: Union[LangfuseSpan, LangfuseGeneration], + langfuse_span_or_generation: Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ], generator: Generator, transform_to_string: Optional[Callable[[Iterable], str]] = None, ) -> Any: @@ -458,7 +490,17 @@ def _wrap_sync_generator_result( async def _wrap_async_generator_result( self, - langfuse_span_or_generation: Union[LangfuseSpan, LangfuseGeneration], + langfuse_span_or_generation: Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ], generator: AsyncGenerator, transform_to_string: Optional[Callable[[Iterable], str]] = None, ) -> AsyncGenerator: diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 34aa4f0d1..003c58706 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -5,7 +5,7 @@ creating, updating, and scoring various types of spans used in AI application tracing. Classes: -- LangfuseSpanWrapper: Abstract base class for all Langfuse spans +- LangfuseObservationWrapper: Abstract base class for all Langfuse spans - LangfuseSpan: Implementation for general-purpose spans - LangfuseGeneration: Specialized span implementation for LLM generations @@ -15,6 +15,7 @@ from datetime import datetime from time import time_ns +import warnings from typing import ( TYPE_CHECKING, Any, @@ -22,6 +23,7 @@ List, Literal, Optional, + Type, Union, cast, overload, @@ -41,11 +43,23 @@ create_span_attributes, create_trace_attributes, ) +from langfuse._client.constants import ( + ObservationTypeLiteral, + ObservationTypeGenerationLike, + ObservationTypeSpanLike, + ObservationTypeLiteralNoEvent, + get_observation_types_list, +) from langfuse.logger import langfuse_logger from langfuse.types import MapValue, ScoreDataType, SpanLevel +# Factory mapping for observation classes +# Note: "event" is handled separately due to special instantiation logic +# Populated after class definitions +_OBSERVATION_CLASS_MAP: Dict[str, Type["LangfuseObservationWrapper"]] = {} + -class LangfuseSpanWrapper: +class LangfuseObservationWrapper: """Abstract base class for all Langfuse span types. This class provides common functionality for all Langfuse span types, including @@ -64,7 +78,7 @@ def __init__( *, otel_span: otel_trace_api.Span, langfuse_client: "Langfuse", - as_type: Literal["span", "generation", "event"], + as_type: ObservationTypeLiteral, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -104,6 +118,7 @@ def __init__( LangfuseOtelSpanAttributes.OBSERVATION_TYPE, as_type ) self._langfuse_client = langfuse_client + self._observation_type = as_type self.trace_id = self._langfuse_client._get_otel_trace_id(otel_span) self.id = self._langfuse_client._get_otel_span_id(otel_span) @@ -128,7 +143,7 @@ def __init__( attributes = {} - if as_type == "generation": + if as_type in get_observation_types_list(ObservationTypeGenerationLike): attributes = create_generation_attributes( input=media_processed_input, output=media_processed_output, @@ -142,9 +157,14 @@ def __init__( usage_details=usage_details, cost_details=cost_details, prompt=prompt, + observation_type=cast( + ObservationTypeGenerationLike, + as_type, + ), ) else: + # For span-like types and events attributes = create_span_attributes( input=media_processed_input, output=media_processed_output, @@ -152,15 +172,24 @@ def __init__( version=version, level=level, status_message=status_message, + observation_type=cast( + Optional[Union[ObservationTypeSpanLike, Literal["event"]]], + as_type + if as_type + in get_observation_types_list(ObservationTypeSpanLike) + or as_type == "event" + else None, + ), ) + # We don't want to overwrite the observation type, and already set it attributes.pop(LangfuseOtelSpanAttributes.OBSERVATION_TYPE, None) self._otel_span.set_attributes( {k: v for k, v in attributes.items() if v is not None} ) - def end(self, *, end_time: Optional[int] = None) -> "LangfuseSpanWrapper": + def end(self, *, end_time: Optional[int] = None) -> "LangfuseObservationWrapper": """End the span, marking it as completed. This method ends the wrapped OpenTelemetry span, marking the end of the @@ -186,7 +215,7 @@ def update_trace( metadata: Optional[Any] = None, tags: Optional[List[str]] = None, public: Optional[bool] = None, - ) -> "LangfuseSpanWrapper": + ) -> "LangfuseObservationWrapper": """Update the trace that this span belongs to. This method updates trace-level attributes of the trace that this span @@ -383,7 +412,7 @@ def _set_processed_span_attributes( self, *, span: otel_trace_api.Span, - as_type: Optional[Literal["span", "generation", "event"]] = None, + as_type: Optional[ObservationTypeLiteral] = None, input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -511,55 +540,6 @@ def _process_media_in_attribute( return data - -class LangfuseSpan(LangfuseSpanWrapper): - """Standard span implementation for general operations in Langfuse. - - This class represents a general-purpose span that can be used to trace - any operation in your application. It extends the base LangfuseSpanWrapper - with specific methods for creating child spans, generations, and updating - span-specific attributes. - """ - - def __init__( - self, - *, - otel_span: otel_trace_api.Span, - langfuse_client: "Langfuse", - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - environment: Optional[str] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - ): - """Initialize a new LangfuseSpan. - - Args: - otel_span: The OpenTelemetry span to wrap - langfuse_client: Reference to the parent Langfuse client - input: Input data for the span (any JSON-serializable object) - output: Output data from the span (any JSON-serializable object) - metadata: Additional metadata to associate with the span - environment: The tracing environment - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - """ - super().__init__( - otel_span=otel_span, - as_type="span", - langfuse_client=langfuse_client, - input=input, - output=output, - metadata=metadata, - environment=environment, - version=version, - level=level, - status_message=status_message, - ) - def update( self, *, @@ -570,33 +550,34 @@ def update( version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, **kwargs: Any, - ) -> "LangfuseSpan": - """Update this span with new information. + ) -> "LangfuseObservationWrapper": + """Update this observation with new information. - This method updates the span with new information that becomes available + This method updates the observation with new information that becomes available during execution, such as outputs, metadata, or status changes. Args: - name: Span name + name: Observation name input: Updated input data for the operation output: Output data from the operation - metadata: Additional metadata to associate with the span + metadata: Additional metadata to associate with the observation version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span + level: Importance level of the observation (info, warning, error) + status_message: Optional status message for the observation + completion_start_time: When the generation started (for generation types) + model: Model identifier used (for generation types) + model_parameters: Parameters passed to the model (for generation types) + usage_details: Token or other usage statistics (for generation types) + cost_details: Cost breakdown for the operation (for generation types) + prompt: Reference to the prompt used (for generation types) **kwargs: Additional keyword arguments (ignored) - - Example: - ```python - span = langfuse.start_span(name="process-data") - try: - # Do work - result = process_data() - span.update(output=result, metadata={"processing_time": 350}) - finally: - span.end() - ``` """ if not self._otel_span.is_recording(): return self @@ -614,147 +595,160 @@ def update( if name: self._otel_span.update_name(name) - attributes = create_span_attributes( - input=processed_input, - output=processed_output, - metadata=processed_metadata, - version=version, - level=level, - status_message=status_message, - ) + if self._observation_type in get_observation_types_list( + ObservationTypeGenerationLike + ): + attributes = create_generation_attributes( + input=processed_input, + output=processed_output, + metadata=processed_metadata, + version=version, + level=level, + status_message=status_message, + observation_type=cast( + ObservationTypeGenerationLike, + self._observation_type, + ), + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ) + else: + # For span-like types and events + attributes = create_span_attributes( + input=processed_input, + output=processed_output, + metadata=processed_metadata, + version=version, + level=level, + status_message=status_message, + observation_type=cast( + Optional[Union[ObservationTypeSpanLike, Literal["event"]]], + self._observation_type + if self._observation_type + in get_observation_types_list(ObservationTypeSpanLike) + or self._observation_type == "event" + else None, + ), + ) self._otel_span.set_attributes(attributes=attributes) return self - def start_span( + @overload + def start_observation( self, + *, name: str, + as_type: Literal["span"], input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, - ) -> "LangfuseSpan": - """Create a new child span. - - This method creates a new child span with this span as the parent. - Unlike start_as_current_span(), this method does not set the new span - as the current span in the context. - - Args: - name: Name of the span (e.g., function or operation name) - input: Input data for the operation - output: Output data from the operation - metadata: Additional metadata to associate with the span - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - - Returns: - A new LangfuseSpan that must be ended with .end() when complete - - Example: - ```python - parent_span = langfuse.start_span(name="process-request") - try: - # Create a child span - child_span = parent_span.start_span(name="validate-input") - try: - # Do validation work - validation_result = validate(request_data) - child_span.update(output=validation_result) - finally: - child_span.end() - - # Continue with parent span - result = process_validated_data(validation_result) - parent_span.update(output=result) - finally: - parent_span.end() - ``` - """ - with otel_trace_api.use_span(self._otel_span): - new_otel_span = self._langfuse_client._otel_tracer.start_span(name=name) - - return LangfuseSpan( - otel_span=new_otel_span, - langfuse_client=self._langfuse_client, - environment=self._environment, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ) + ) -> "LangfuseSpan": ... - def start_as_current_span( + @overload + def start_observation( self, *, name: str, + as_type: Literal["generation"], input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, - ) -> _AgnosticContextManager["LangfuseSpan"]: - """Create a new child span and set it as the current span in a context manager. - - This method creates a new child span and sets it as the current span within - a context manager. It should be used with a 'with' statement to automatically - manage the span's lifecycle. + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> "LangfuseGeneration": ... - Args: - name: Name of the span (e.g., function or operation name) - input: Input data for the operation - output: Output data from the operation - metadata: Additional metadata to associate with the span - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["agent"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseAgent": ... - Returns: - A context manager that yields a new LangfuseSpan + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["tool"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseTool": ... - Example: - ```python - with langfuse.start_as_current_span(name="process-request") as parent_span: - # Parent span is active here + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["chain"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseChain": ... - # Create a child span with context management - with parent_span.start_as_current_span(name="validate-input") as child_span: - # Child span is active here - validation_result = validate(request_data) - child_span.update(output=validation_result) + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["retriever"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseRetriever": ... - # Back to parent span context - result = process_validated_data(validation_result) - parent_span.update(output=result) - ``` - """ - return cast( - _AgnosticContextManager["LangfuseSpan"], - self._langfuse_client._create_span_with_parent_context( - name=name, - as_type="span", - remote_parent_span=None, - parent=self._otel_span, - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ), - ) + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["evaluator"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseEvaluator": ... - def start_generation( + @overload + def start_observation( self, *, name: str, + as_type: Literal["embedding"], input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -767,15 +761,560 @@ def start_generation( usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, - ) -> "LangfuseGeneration": - """Create a new child generation span. + ) -> "LangfuseEmbedding": ... - This method creates a new child generation span with this span as the parent. - Generation spans are specialized for AI/LLM operations and include additional - fields for model information, usage stats, and costs. + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["guardrail"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseGuardrail": ... - Unlike start_as_current_generation(), this method does not set the new span - as the current span in the context. + @overload + def start_observation( + self, + *, + name: str, + as_type: Literal["event"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseEvent": ... + + def start_observation( + self, + *, + name: str, + as_type: ObservationTypeLiteral, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> Union[ + "LangfuseSpan", + "LangfuseGeneration", + "LangfuseAgent", + "LangfuseTool", + "LangfuseChain", + "LangfuseRetriever", + "LangfuseEvaluator", + "LangfuseEmbedding", + "LangfuseGuardrail", + "LangfuseEvent", + ]: + """Create a new child observation of the specified type. + + This is the generic method for creating any type of child observation. + Unlike start_as_current_observation(), this method does not set the new + observation as the current observation in the context. + + Args: + name: Name of the observation + as_type: Type of observation to create + input: Input data for the operation + output: Output data from the operation + metadata: Additional metadata to associate with the observation + version: Version identifier for the code or component + level: Importance level of the observation (info, warning, error) + status_message: Optional status message for the observation + completion_start_time: When the model started generating (for generation types) + model: Name/identifier of the AI model used (for generation types) + model_parameters: Parameters used for the model (for generation types) + usage_details: Token usage information (for generation types) + cost_details: Cost information (for generation types) + prompt: Associated prompt template (for generation types) + + Returns: + A new observation of the specified type that must be ended with .end() + """ + if as_type == "event": + timestamp = time_ns() + event_span = self._langfuse_client._otel_tracer.start_span( + name=name, start_time=timestamp + ) + return cast( + LangfuseEvent, + LangfuseEvent( + otel_span=event_span, + langfuse_client=self._langfuse_client, + input=input, + output=output, + metadata=metadata, + environment=self._environment, + version=version, + level=level, + status_message=status_message, + ).end(end_time=timestamp), + ) + + observation_class = _OBSERVATION_CLASS_MAP.get(as_type) + if not observation_class: + langfuse_logger.warning( + f"Unknown observation type: {as_type}, falling back to LangfuseSpan" + ) + observation_class = LangfuseSpan + + with otel_trace_api.use_span(self._otel_span): + new_otel_span = self._langfuse_client._otel_tracer.start_span(name=name) + + common_args = { + "otel_span": new_otel_span, + "langfuse_client": self._langfuse_client, + "environment": self._environment, + "input": input, + "output": output, + "metadata": metadata, + "version": version, + "level": level, + "status_message": status_message, + } + + if as_type in get_observation_types_list(ObservationTypeGenerationLike): + common_args.update( + { + "completion_start_time": completion_start_time, + "model": model, + "model_parameters": model_parameters, + "usage_details": usage_details, + "cost_details": cost_details, + "prompt": prompt, + } + ) + + return observation_class(**common_args) # type: ignore[no-any-return,return-value,arg-type] + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["span"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseSpan"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["generation"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> _AgnosticContextManager["LangfuseGeneration"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["embedding"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> _AgnosticContextManager["LangfuseEmbedding"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["agent"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseAgent"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["tool"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseTool"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["chain"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseChain"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["retriever"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseRetriever"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["evaluator"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseEvaluator"]: ... + + @overload + def start_as_current_observation( + self, + *, + name: str, + as_type: Literal["guardrail"], + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseGuardrail"]: ... + + def start_as_current_observation( # type: ignore[misc] + self, + *, + name: str, + as_type: ObservationTypeLiteralNoEvent, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + # TODO: or union of context managers? + ) -> _AgnosticContextManager[ + Union[ + "LangfuseSpan", + "LangfuseGeneration", + "LangfuseAgent", + "LangfuseTool", + "LangfuseChain", + "LangfuseRetriever", + "LangfuseEvaluator", + "LangfuseEmbedding", + "LangfuseGuardrail", + ] + ]: + """Create a new child observation and set it as the current observation in a context manager. + + This is the generic method for creating any type of child observation with + context management. It delegates to the client's _create_span_with_parent_context method. + + Args: + name: Name of the observation + as_type: Type of observation to create + input: Input data for the operation + output: Output data from the operation + metadata: Additional metadata to associate with the observation + version: Version identifier for the code or component + level: Importance level of the observation (info, warning, error) + status_message: Optional status message for the observation + completion_start_time: When the model started generating (for generation types) + model: Name/identifier of the AI model used (for generation types) + model_parameters: Parameters used for the model (for generation types) + usage_details: Token usage information (for generation types) + cost_details: Cost information (for generation types) + prompt: Associated prompt template (for generation types) + + Returns: + A context manager that yields a new observation of the specified type + """ + return self._langfuse_client._create_span_with_parent_context( + name=name, + as_type=as_type, + remote_parent_span=None, + parent=self._otel_span, + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ) + + +class LangfuseSpan(LangfuseObservationWrapper): + """Standard span implementation for general operations in Langfuse. + + This class represents a general-purpose span that can be used to trace + any operation in your application. It extends the base LangfuseObservationWrapper + with specific methods for creating child spans, generations, and updating + span-specific attributes. If possible, use a more specific type for + better observability and insights. + """ + + def __init__( + self, + *, + otel_span: otel_trace_api.Span, + langfuse_client: "Langfuse", + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + environment: Optional[str] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ): + """Initialize a new LangfuseSpan. + + Args: + otel_span: The OpenTelemetry span to wrap + langfuse_client: Reference to the parent Langfuse client + input: Input data for the span (any JSON-serializable object) + output: Output data from the span (any JSON-serializable object) + metadata: Additional metadata to associate with the span + environment: The tracing environment + version: Version identifier for the code or component + level: Importance level of the span (info, warning, error) + status_message: Optional status message for the span + """ + super().__init__( + otel_span=otel_span, + as_type="span", + langfuse_client=langfuse_client, + input=input, + output=output, + metadata=metadata, + environment=environment, + version=version, + level=level, + status_message=status_message, + ) + + def start_span( + self, + name: str, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> "LangfuseSpan": + """Create a new child span. + + This method creates a new child span with this span as the parent. + Unlike start_as_current_span(), this method does not set the new span + as the current span in the context. + + Args: + name: Name of the span (e.g., function or operation name) + input: Input data for the operation + output: Output data from the operation + metadata: Additional metadata to associate with the span + version: Version identifier for the code or component + level: Importance level of the span (info, warning, error) + status_message: Optional status message for the span + + Returns: + A new LangfuseSpan that must be ended with .end() when complete + + Example: + ```python + parent_span = langfuse.start_span(name="process-request") + try: + # Create a child span + child_span = parent_span.start_span(name="validate-input") + try: + # Do validation work + validation_result = validate(request_data) + child_span.update(output=validation_result) + finally: + child_span.end() + + # Continue with parent span + result = process_validated_data(validation_result) + parent_span.update(output=result) + finally: + parent_span.end() + ``` + """ + return self.start_observation( + name=name, + as_type="span", + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + ) + + def start_as_current_span( + self, + *, + name: str, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ) -> _AgnosticContextManager["LangfuseSpan"]: + """[DEPRECATED] Create a new child span and set it as the current span in a context manager. + + DEPRECATED: This method is deprecated and will be removed in a future version. + Use start_as_current_observation(as_type='span') instead. + + This method creates a new child span and sets it as the current span within + a context manager. It should be used with a 'with' statement to automatically + manage the span's lifecycle. + + Args: + name: Name of the span (e.g., function or operation name) + input: Input data for the operation + output: Output data from the operation + metadata: Additional metadata to associate with the span + version: Version identifier for the code or component + level: Importance level of the span (info, warning, error) + status_message: Optional status message for the span + + Returns: + A context manager that yields a new LangfuseSpan + + Example: + ```python + with langfuse.start_as_current_span(name="process-request") as parent_span: + # Parent span is active here + + # Create a child span with context management + with parent_span.start_as_current_span(name="validate-input") as child_span: + # Child span is active here + validation_result = validate(request_data) + child_span.update(output=validation_result) + + # Back to parent span context + result = process_validated_data(validation_result) + parent_span.update(output=result) + ``` + """ + warnings.warn( + "start_as_current_span is deprecated and will be removed in a future version. " + "Use start_as_current_observation(as_type='span') instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.start_as_current_observation( + name=name, + as_type="span", + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + ) + + def start_generation( + self, + *, + name: str, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + ) -> "LangfuseGeneration": + """[DEPRECATED] Create a new child generation span. + + DEPRECATED: This method is deprecated and will be removed in a future version. + Use start_observation(as_type='generation') instead. + + This method creates a new child generation span with this span as the parent. + Generation spans are specialized for AI/LLM operations and include additional + fields for model information, usage stats, and costs. + + Unlike start_as_current_generation(), this method does not set the new span + as the current span in the context. Args: name: Name of the generation operation @@ -825,13 +1364,15 @@ def start_generation( span.end() ``` """ - with otel_trace_api.use_span(self._otel_span): - new_otel_span = self._langfuse_client._otel_tracer.start_span(name=name) - - return LangfuseGeneration( - otel_span=new_otel_span, - langfuse_client=self._langfuse_client, - environment=self._environment, + warnings.warn( + "start_generation is deprecated and will be removed in a future version. " + "Use start_observation(as_type='generation') instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.start_observation( + name=name, + as_type="generation", input=input, output=output, metadata=metadata, @@ -863,7 +1404,10 @@ def start_as_current_generation( cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, ) -> _AgnosticContextManager["LangfuseGeneration"]: - """Create a new child generation span and set it as the current span in a context manager. + """[DEPRECATED] Create a new child generation span and set it as the current span in a context manager. + + DEPRECATED: This method is deprecated and will be removed in a future version. + Use start_as_current_observation(as_type='generation') instead. This method creates a new child generation span and sets it as the current span within a context manager. Generation spans are specialized for AI/LLM operations @@ -915,13 +1459,15 @@ def start_as_current_generation( span.update(output={"answer": response.text, "source": "gpt-4"}) ``` """ - return cast( - _AgnosticContextManager["LangfuseGeneration"], - self._langfuse_client._create_span_with_parent_context( - name=name, - as_type="generation", - remote_parent_span=None, - parent=self._otel_span, + warnings.warn( + "start_as_current_generation is deprecated and will be removed in a future version. " + "Use start_as_current_observation(as_type='generation') instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.start_as_current_observation( + name=name, + as_type="generation", input=input, output=output, metadata=metadata, @@ -934,8 +1480,7 @@ def start_as_current_generation( usage_details=usage_details, cost_details=cost_details, prompt=prompt, - ), - ) + ) def create_event( self, @@ -990,11 +1535,11 @@ def create_event( ) -class LangfuseGeneration(LangfuseSpanWrapper): +class LangfuseGeneration(LangfuseObservationWrapper): """Specialized span implementation for AI model generations in Langfuse. This class represents a generation span specifically designed for tracking - AI/LLM operations. It extends the base LangfuseSpanWrapper with specialized + AI/LLM operations. It extends the base LangfuseObservationWrapper with specialized attributes for model details, token usage, and costs. """ @@ -1037,8 +1582,8 @@ def __init__( prompt: Associated prompt template from Langfuse prompt management """ super().__init__( - otel_span=otel_span, as_type="generation", + otel_span=otel_span, langfuse_client=langfuse_client, input=input, output=output, @@ -1055,110 +1600,8 @@ def __init__( prompt=prompt, ) - def update( - self, - *, - name: Optional[str] = None, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - completion_start_time: Optional[datetime] = None, - model: Optional[str] = None, - model_parameters: Optional[Dict[str, MapValue]] = None, - usage_details: Optional[Dict[str, int]] = None, - cost_details: Optional[Dict[str, float]] = None, - prompt: Optional[PromptClient] = None, - **kwargs: Dict[str, Any], - ) -> "LangfuseGeneration": - """Update this generation span with new information. - - This method updates the generation span with new information that becomes - available during or after the model generation, such as model outputs, - token usage statistics, or cost details. - - Args: - name: The generation name - input: Updated input data for the model - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management - **kwargs: Additional keyword arguments (ignored) - - Example: - ```python - generation = langfuse.start_generation( - name="answer-generation", - model="gpt-4", - input={"prompt": "Explain quantum computing"} - ) - try: - # Call model API - response = llm.generate(...) - - # Update with results - generation.update( - output=response.text, - usage_details={ - "prompt_tokens": response.usage.prompt_tokens, - "completion_tokens": response.usage.completion_tokens, - "total_tokens": response.usage.total_tokens - }, - cost_details={ - "total_cost": 0.0035 - } - ) - finally: - generation.end() - ``` - """ - if not self._otel_span.is_recording(): - return self - - processed_input = self._process_media_and_apply_mask( - data=input, field="input", span=self._otel_span - ) - processed_output = self._process_media_and_apply_mask( - data=output, field="output", span=self._otel_span - ) - processed_metadata = self._process_media_and_apply_mask( - data=metadata, field="metadata", span=self._otel_span - ) - - if name: - self._otel_span.update_name(name) - - attributes = create_generation_attributes( - input=processed_input, - output=processed_output, - metadata=processed_metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ) - - self._otel_span.set_attributes(attributes=attributes) - - return self - -class LangfuseEvent(LangfuseSpanWrapper): +class LangfuseEvent(LangfuseObservationWrapper): """Specialized span implementation for Langfuse Events.""" def __init__( @@ -1199,3 +1642,111 @@ def __init__( level=level, status_message=status_message, ) + + def update( + self, + *, + name: Optional[str] = None, + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + completion_start_time: Optional[datetime] = None, + model: Optional[str] = None, + model_parameters: Optional[Dict[str, MapValue]] = None, + usage_details: Optional[Dict[str, int]] = None, + cost_details: Optional[Dict[str, float]] = None, + prompt: Optional[PromptClient] = None, + **kwargs: Any, + ) -> "LangfuseEvent": + """Update is not allowed for LangfuseEvent because events cannot be updated. + + This method logs a warning and returns self without making changes. + + Returns: + self: Returns the unchanged LangfuseEvent instance + """ + langfuse_logger.warning( + "Attempted to update LangfuseEvent observation. Events cannot be updated after creation." + ) + return self + + +class LangfuseAgent(LangfuseObservationWrapper): + """Agent observation for reasoning blocks that act on tools using LLM guidance.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseAgent span.""" + kwargs["as_type"] = "agent" + super().__init__(**kwargs) + + +class LangfuseTool(LangfuseObservationWrapper): + """Tool observation representing external tool calls, e.g., calling a weather API.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseTool span.""" + kwargs["as_type"] = "tool" + super().__init__(**kwargs) + + +class LangfuseChain(LangfuseObservationWrapper): + """Chain observation for connecting LLM application steps, e.g. passing context from retriever to LLM.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseChain span.""" + kwargs["as_type"] = "chain" + super().__init__(**kwargs) + + +class LangfuseRetriever(LangfuseObservationWrapper): + """Retriever observation for data retrieval steps, e.g. vector store or database queries.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseRetriever span.""" + kwargs["as_type"] = "retriever" + super().__init__(**kwargs) + + +class LangfuseEmbedding(LangfuseObservationWrapper): + """Embedding observation for LLM embedding calls, typically used before retrieval.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseEmbedding span.""" + kwargs["as_type"] = "embedding" + super().__init__(**kwargs) + + +class LangfuseEvaluator(LangfuseObservationWrapper): + """Evaluator observation for assessing relevance, correctness, or helpfulness of LLM outputs.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseEvaluator span.""" + kwargs["as_type"] = "evaluator" + super().__init__(**kwargs) + + +class LangfuseGuardrail(LangfuseObservationWrapper): + """Guardrail observation for protection e.g. against jailbreaks or offensive content.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize a new LangfuseGuardrail span.""" + kwargs["as_type"] = "guardrail" + super().__init__(**kwargs) + + +_OBSERVATION_CLASS_MAP.update( + { + "span": LangfuseSpan, + "generation": LangfuseGeneration, + "agent": LangfuseAgent, + "tool": LangfuseTool, + "chain": LangfuseChain, + "retriever": LangfuseRetriever, + "evaluator": LangfuseEvaluator, + "embedding": LangfuseEmbedding, + "guardrail": LangfuseGuardrail, + } +) diff --git a/langfuse/api/README.md b/langfuse/api/README.md index feb6512ef..d7fa24a33 100644 --- a/langfuse/api/README.md +++ b/langfuse/api/README.md @@ -16,7 +16,7 @@ pip install langfuse Instantiate and use the client with the following: ```python -from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest +from langfuse import CreateAnnotationQueueRequest from langfuse.client import FernLangfuse client = FernLangfuse( @@ -27,11 +27,10 @@ client = FernLangfuse( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) -client.annotation_queues.create_queue_item( - queue_id="queueId", - request=CreateAnnotationQueueItemRequest( - object_id="objectId", - object_type=AnnotationQueueObjectType.TRACE, +client.annotation_queues.create_queue( + request=CreateAnnotationQueueRequest( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], ), ) ``` @@ -43,7 +42,7 @@ The SDK also exports an `async` client so that you can make non-blocking calls t ```python import asyncio -from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest +from langfuse import CreateAnnotationQueueRequest from langfuse.client import AsyncFernLangfuse client = AsyncFernLangfuse( @@ -57,11 +56,10 @@ client = AsyncFernLangfuse( async def main() -> None: - await client.annotation_queues.create_queue_item( - queue_id="queueId", - request=CreateAnnotationQueueItemRequest( - object_id="objectId", - object_type=AnnotationQueueObjectType.TRACE, + await client.annotation_queues.create_queue( + request=CreateAnnotationQueueRequest( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], ), ) @@ -78,7 +76,7 @@ will be thrown. from .api_error import ApiError try: - client.annotation_queues.create_queue_item(...) + client.annotation_queues.create_queue(...) except ApiError as e: print(e.status_code) print(e.body) @@ -101,7 +99,7 @@ A request is deemed retriable when any of the following HTTP status codes is ret Use the `max_retries` request option to configure this behavior. ```python -client.annotation_queues.create_queue_item(...,{ +client.annotation_queues.create_queue(...,{ max_retries=1 }) ``` @@ -118,7 +116,7 @@ client = FernLangfuse(..., { timeout=20.0 }, ) # Override timeout for a specific method -client.annotation_queues.create_queue_item(...,{ +client.annotation_queues.create_queue(...,{ timeout_in_seconds=1 }) ``` diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 2a274a811..4f43e45f1 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -3,6 +3,7 @@ from .resources import ( AccessDeniedError, AnnotationQueue, + AnnotationQueueAssignmentRequest, AnnotationQueueItem, AnnotationQueueObjectType, AnnotationQueueStatus, @@ -28,7 +29,9 @@ Comment, CommentObjectType, ConfigCategory, + CreateAnnotationQueueAssignmentResponse, CreateAnnotationQueueItemRequest, + CreateAnnotationQueueRequest, CreateChatPromptRequest, CreateCommentRequest, CreateCommentResponse, @@ -57,6 +60,7 @@ DatasetRunItem, DatasetRunWithItems, DatasetStatus, + DeleteAnnotationQueueAssignmentResponse, DeleteAnnotationQueueItemResponse, DeleteDatasetItemResponse, DeleteDatasetRunResponse, @@ -93,6 +97,8 @@ IngestionResponse, IngestionSuccess, IngestionUsage, + LlmAdapter, + LlmConnection, MapValue, MediaContentType, MembershipRequest, @@ -126,6 +132,7 @@ PaginatedDatasetRunItems, PaginatedDatasetRuns, PaginatedDatasets, + PaginatedLlmConnections, PaginatedModels, PaginatedSessions, PatchMediaBody, @@ -185,6 +192,7 @@ UpdateObservationEvent, UpdateSpanBody, UpdateSpanEvent, + UpsertLlmConnectionRequest, Usage, UsageDetails, UserMeta, @@ -196,6 +204,7 @@ datasets, health, ingestion, + llm_connections, media, metrics, models, @@ -216,6 +225,7 @@ __all__ = [ "AccessDeniedError", "AnnotationQueue", + "AnnotationQueueAssignmentRequest", "AnnotationQueueItem", "AnnotationQueueObjectType", "AnnotationQueueStatus", @@ -241,7 +251,9 @@ "Comment", "CommentObjectType", "ConfigCategory", + "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", + "CreateAnnotationQueueRequest", "CreateChatPromptRequest", "CreateCommentRequest", "CreateCommentResponse", @@ -270,6 +282,7 @@ "DatasetRunItem", "DatasetRunWithItems", "DatasetStatus", + "DeleteAnnotationQueueAssignmentResponse", "DeleteAnnotationQueueItemResponse", "DeleteDatasetItemResponse", "DeleteDatasetRunResponse", @@ -306,6 +319,8 @@ "IngestionResponse", "IngestionSuccess", "IngestionUsage", + "LlmAdapter", + "LlmConnection", "MapValue", "MediaContentType", "MembershipRequest", @@ -339,6 +354,7 @@ "PaginatedDatasetRunItems", "PaginatedDatasetRuns", "PaginatedDatasets", + "PaginatedLlmConnections", "PaginatedModels", "PaginatedSessions", "PatchMediaBody", @@ -398,6 +414,7 @@ "UpdateObservationEvent", "UpdateSpanBody", "UpdateSpanEvent", + "UpsertLlmConnectionRequest", "Usage", "UsageDetails", "UserMeta", @@ -409,6 +426,7 @@ "datasets", "health", "ingestion", + "llm_connections", "media", "metrics", "models", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 87b46c2f8..f18caba1c 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -18,6 +18,10 @@ from .resources.datasets.client import AsyncDatasetsClient, DatasetsClient from .resources.health.client import AsyncHealthClient, HealthClient from .resources.ingestion.client import AsyncIngestionClient, IngestionClient +from .resources.llm_connections.client import ( + AsyncLlmConnectionsClient, + LlmConnectionsClient, +) from .resources.media.client import AsyncMediaClient, MediaClient from .resources.metrics.client import AsyncMetricsClient, MetricsClient from .resources.models.client import AsyncModelsClient, ModelsClient @@ -120,6 +124,7 @@ def __init__( self.datasets = DatasetsClient(client_wrapper=self._client_wrapper) self.health = HealthClient(client_wrapper=self._client_wrapper) self.ingestion = IngestionClient(client_wrapper=self._client_wrapper) + self.llm_connections = LlmConnectionsClient(client_wrapper=self._client_wrapper) self.media = MediaClient(client_wrapper=self._client_wrapper) self.metrics = MetricsClient(client_wrapper=self._client_wrapper) self.models = ModelsClient(client_wrapper=self._client_wrapper) @@ -218,6 +223,9 @@ def __init__( self.datasets = AsyncDatasetsClient(client_wrapper=self._client_wrapper) self.health = AsyncHealthClient(client_wrapper=self._client_wrapper) self.ingestion = AsyncIngestionClient(client_wrapper=self._client_wrapper) + self.llm_connections = AsyncLlmConnectionsClient( + client_wrapper=self._client_wrapper + ) self.media = AsyncMediaClient(client_wrapper=self._client_wrapper) self.metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) self.models = AsyncModelsClient(client_wrapper=self._client_wrapper) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 29a7db88b..ce4c4ecd8 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -77,6 +77,85 @@ client.annotation_queues.list_queues() + + + + +
client.annotation_queues.create_queue(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create an annotation queue +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import CreateAnnotationQueueRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.annotation_queues.create_queue( + request=CreateAnnotationQueueRequest( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `CreateAnnotationQueueRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -601,6 +680,180 @@ client.annotation_queues.delete_queue_item( + + + + +
client.annotation_queues.create_queue_assignment(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create an assignment for a user to an annotation queue +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import AnnotationQueueAssignmentRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.annotation_queues.create_queue_assignment( + queue_id="queueId", + request=AnnotationQueueAssignmentRequest( + user_id="userId", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**queue_id:** `str` — The unique identifier of the annotation queue + +
+
+ +
+
+ +**request:** `AnnotationQueueAssignmentRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.annotation_queues.delete_queue_assignment(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Delete an assignment for a user to an annotation queue +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import AnnotationQueueAssignmentRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.annotation_queues.delete_queue_assignment( + queue_id="queueId", + request=AnnotationQueueAssignmentRequest( + user_id="userId", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**queue_id:** `str` — The unique identifier of the annotation queue + +
+
+ +
+
+ +**request:** `AnnotationQueueAssignmentRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -2047,6 +2300,168 @@ client.ingestion.batch( + + + + +## LlmConnections +
client.llm_connections.list(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get all LLM connections in a project +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.llm_connections.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — page number, starts at 1 + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — limit of items per page + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.llm_connections.upsert(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create or update an LLM connection. The connection is upserted on provider. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import LlmAdapter, UpsertLlmConnectionRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.llm_connections.upsert( + request=UpsertLlmConnectionRequest( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `UpsertLlmConnectionRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -2907,6 +3322,14 @@ client.observations.get_many()
+**level:** `typing.Optional[ObservationLevel]` — Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + +
+
+ +
+
+ **parent_observation_id:** `typing.Optional[str]`
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index 453774283..062c933be 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -9,6 +9,7 @@ datasets, health, ingestion, + llm_connections, media, metrics, models, @@ -27,10 +28,14 @@ ) from .annotation_queues import ( AnnotationQueue, + AnnotationQueueAssignmentRequest, AnnotationQueueItem, AnnotationQueueObjectType, AnnotationQueueStatus, + CreateAnnotationQueueAssignmentResponse, CreateAnnotationQueueItemRequest, + CreateAnnotationQueueRequest, + DeleteAnnotationQueueAssignmentResponse, DeleteAnnotationQueueItemResponse, PaginatedAnnotationQueueItems, PaginatedAnnotationQueues, @@ -143,6 +148,12 @@ UpdateSpanEvent, UsageDetails, ) +from .llm_connections import ( + LlmAdapter, + LlmConnection, + PaginatedLlmConnections, + UpsertLlmConnectionRequest, +) from .media import ( GetMediaResponse, GetMediaUploadUrlRequest, @@ -228,6 +239,7 @@ __all__ = [ "AccessDeniedError", "AnnotationQueue", + "AnnotationQueueAssignmentRequest", "AnnotationQueueItem", "AnnotationQueueObjectType", "AnnotationQueueStatus", @@ -253,7 +265,9 @@ "Comment", "CommentObjectType", "ConfigCategory", + "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", + "CreateAnnotationQueueRequest", "CreateChatPromptRequest", "CreateCommentRequest", "CreateCommentResponse", @@ -282,6 +296,7 @@ "DatasetRunItem", "DatasetRunWithItems", "DatasetStatus", + "DeleteAnnotationQueueAssignmentResponse", "DeleteAnnotationQueueItemResponse", "DeleteDatasetItemResponse", "DeleteDatasetRunResponse", @@ -318,6 +333,8 @@ "IngestionResponse", "IngestionSuccess", "IngestionUsage", + "LlmAdapter", + "LlmConnection", "MapValue", "MediaContentType", "MembershipRequest", @@ -351,6 +368,7 @@ "PaginatedDatasetRunItems", "PaginatedDatasetRuns", "PaginatedDatasets", + "PaginatedLlmConnections", "PaginatedModels", "PaginatedSessions", "PatchMediaBody", @@ -410,6 +428,7 @@ "UpdateObservationEvent", "UpdateSpanBody", "UpdateSpanEvent", + "UpsertLlmConnectionRequest", "Usage", "UsageDetails", "UserMeta", @@ -421,6 +440,7 @@ "datasets", "health", "ingestion", + "llm_connections", "media", "metrics", "models", diff --git a/langfuse/api/resources/annotation_queues/__init__.py b/langfuse/api/resources/annotation_queues/__init__.py index 50f79e893..eed891727 100644 --- a/langfuse/api/resources/annotation_queues/__init__.py +++ b/langfuse/api/resources/annotation_queues/__init__.py @@ -2,10 +2,14 @@ from .types import ( AnnotationQueue, + AnnotationQueueAssignmentRequest, AnnotationQueueItem, AnnotationQueueObjectType, AnnotationQueueStatus, + CreateAnnotationQueueAssignmentResponse, CreateAnnotationQueueItemRequest, + CreateAnnotationQueueRequest, + DeleteAnnotationQueueAssignmentResponse, DeleteAnnotationQueueItemResponse, PaginatedAnnotationQueueItems, PaginatedAnnotationQueues, @@ -14,10 +18,14 @@ __all__ = [ "AnnotationQueue", + "AnnotationQueueAssignmentRequest", "AnnotationQueueItem", "AnnotationQueueObjectType", "AnnotationQueueStatus", + "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", + "CreateAnnotationQueueRequest", + "DeleteAnnotationQueueAssignmentResponse", "DeleteAnnotationQueueItemResponse", "PaginatedAnnotationQueueItems", "PaginatedAnnotationQueues", diff --git a/langfuse/api/resources/annotation_queues/client.py b/langfuse/api/resources/annotation_queues/client.py index bc1fd287f..97c7c2216 100644 --- a/langfuse/api/resources/annotation_queues/client.py +++ b/langfuse/api/resources/annotation_queues/client.py @@ -14,9 +14,17 @@ from ..commons.errors.not_found_error import NotFoundError from ..commons.errors.unauthorized_error import UnauthorizedError from .types.annotation_queue import AnnotationQueue +from .types.annotation_queue_assignment_request import AnnotationQueueAssignmentRequest from .types.annotation_queue_item import AnnotationQueueItem from .types.annotation_queue_status import AnnotationQueueStatus +from .types.create_annotation_queue_assignment_response import ( + CreateAnnotationQueueAssignmentResponse, +) from .types.create_annotation_queue_item_request import CreateAnnotationQueueItemRequest +from .types.create_annotation_queue_request import CreateAnnotationQueueRequest +from .types.delete_annotation_queue_assignment_response import ( + DeleteAnnotationQueueAssignmentResponse, +) from .types.delete_annotation_queue_item_response import ( DeleteAnnotationQueueItemResponse, ) @@ -105,6 +113,79 @@ def list_queues( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def create_queue( + self, + *, + request: CreateAnnotationQueueRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueue: + """ + Create an annotation queue + + Parameters + ---------- + request : CreateAnnotationQueueRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueue + + Examples + -------- + from langfuse import CreateAnnotationQueueRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.create_queue( + request=CreateAnnotationQueueRequest( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/annotation-queues", + method="POST", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(AnnotationQueue, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + def get_queue( self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AnnotationQueue: @@ -559,6 +640,164 @@ def delete_queue_item( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def create_queue_assignment( + self, + queue_id: str, + *, + request: AnnotationQueueAssignmentRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateAnnotationQueueAssignmentResponse: + """ + Create an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request : AnnotationQueueAssignmentRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateAnnotationQueueAssignmentResponse + + Examples + -------- + from langfuse import AnnotationQueueAssignmentRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.create_queue_assignment( + queue_id="queueId", + request=AnnotationQueueAssignmentRequest( + user_id="userId", + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="POST", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + CreateAnnotationQueueAssignmentResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def delete_queue_assignment( + self, + queue_id: str, + *, + request: AnnotationQueueAssignmentRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteAnnotationQueueAssignmentResponse: + """ + Delete an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request : AnnotationQueueAssignmentRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteAnnotationQueueAssignmentResponse + + Examples + -------- + from langfuse import AnnotationQueueAssignmentRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.delete_queue_assignment( + queue_id="queueId", + request=AnnotationQueueAssignmentRequest( + user_id="userId", + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="DELETE", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + DeleteAnnotationQueueAssignmentResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + class AsyncAnnotationQueuesClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -645,6 +884,87 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + async def create_queue( + self, + *, + request: CreateAnnotationQueueRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueue: + """ + Create an annotation queue + + Parameters + ---------- + request : CreateAnnotationQueueRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueue + + Examples + -------- + import asyncio + + from langfuse import CreateAnnotationQueueRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.create_queue( + request=CreateAnnotationQueueRequest( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/annotation-queues", + method="POST", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(AnnotationQueue, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + async def get_queue( self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AnnotationQueue: @@ -1146,3 +1466,177 @@ async def main() -> None: except JSONDecodeError: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + + async def create_queue_assignment( + self, + queue_id: str, + *, + request: AnnotationQueueAssignmentRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateAnnotationQueueAssignmentResponse: + """ + Create an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request : AnnotationQueueAssignmentRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateAnnotationQueueAssignmentResponse + + Examples + -------- + import asyncio + + from langfuse import AnnotationQueueAssignmentRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.create_queue_assignment( + queue_id="queueId", + request=AnnotationQueueAssignmentRequest( + user_id="userId", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="POST", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + CreateAnnotationQueueAssignmentResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def delete_queue_assignment( + self, + queue_id: str, + *, + request: AnnotationQueueAssignmentRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteAnnotationQueueAssignmentResponse: + """ + Delete an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request : AnnotationQueueAssignmentRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteAnnotationQueueAssignmentResponse + + Examples + -------- + import asyncio + + from langfuse import AnnotationQueueAssignmentRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.delete_queue_assignment( + queue_id="queueId", + request=AnnotationQueueAssignmentRequest( + user_id="userId", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="DELETE", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + DeleteAnnotationQueueAssignmentResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/annotation_queues/types/__init__.py b/langfuse/api/resources/annotation_queues/types/__init__.py index 110b991cf..9f9ce37dd 100644 --- a/langfuse/api/resources/annotation_queues/types/__init__.py +++ b/langfuse/api/resources/annotation_queues/types/__init__.py @@ -1,10 +1,18 @@ # This file was auto-generated by Fern from our API Definition. from .annotation_queue import AnnotationQueue +from .annotation_queue_assignment_request import AnnotationQueueAssignmentRequest from .annotation_queue_item import AnnotationQueueItem from .annotation_queue_object_type import AnnotationQueueObjectType from .annotation_queue_status import AnnotationQueueStatus +from .create_annotation_queue_assignment_response import ( + CreateAnnotationQueueAssignmentResponse, +) from .create_annotation_queue_item_request import CreateAnnotationQueueItemRequest +from .create_annotation_queue_request import CreateAnnotationQueueRequest +from .delete_annotation_queue_assignment_response import ( + DeleteAnnotationQueueAssignmentResponse, +) from .delete_annotation_queue_item_response import DeleteAnnotationQueueItemResponse from .paginated_annotation_queue_items import PaginatedAnnotationQueueItems from .paginated_annotation_queues import PaginatedAnnotationQueues @@ -12,10 +20,14 @@ __all__ = [ "AnnotationQueue", + "AnnotationQueueAssignmentRequest", "AnnotationQueueItem", "AnnotationQueueObjectType", "AnnotationQueueStatus", + "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", + "CreateAnnotationQueueRequest", + "DeleteAnnotationQueueAssignmentResponse", "DeleteAnnotationQueueItemResponse", "PaginatedAnnotationQueueItems", "PaginatedAnnotationQueues", diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py b/langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py new file mode 100644 index 000000000..aa3980438 --- /dev/null +++ b/langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class AnnotationQueueAssignmentRequest(pydantic_v1.BaseModel): + user_id: str = pydantic_v1.Field(alias="userId") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py new file mode 100644 index 000000000..ae6a46862 --- /dev/null +++ b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class CreateAnnotationQueueAssignmentResponse(pydantic_v1.BaseModel): + user_id: str = pydantic_v1.Field(alias="userId") + queue_id: str = pydantic_v1.Field(alias="queueId") + project_id: str = pydantic_v1.Field(alias="projectId") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py new file mode 100644 index 000000000..7f793cea2 --- /dev/null +++ b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class CreateAnnotationQueueRequest(pydantic_v1.BaseModel): + name: str + description: typing.Optional[str] = None + score_config_ids: typing.List[str] = pydantic_v1.Field(alias="scoreConfigIds") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py b/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py new file mode 100644 index 000000000..e348d546c --- /dev/null +++ b/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class DeleteAnnotationQueueAssignmentResponse(pydantic_v1.BaseModel): + success: bool + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/map_value.py b/langfuse/api/resources/commons/types/map_value.py index 46115a967..e1e771a9b 100644 --- a/langfuse/api/resources/commons/types/map_value.py +++ b/langfuse/api/resources/commons/types/map_value.py @@ -5,7 +5,6 @@ MapValue = typing.Union[ typing.Optional[str], typing.Optional[int], - typing.Optional[float], typing.Optional[bool], typing.Optional[typing.List[str]], ] diff --git a/langfuse/api/resources/ingestion/types/observation_type.py b/langfuse/api/resources/ingestion/types/observation_type.py index 0af377c3c..2f11300ff 100644 --- a/langfuse/api/resources/ingestion/types/observation_type.py +++ b/langfuse/api/resources/ingestion/types/observation_type.py @@ -10,12 +10,26 @@ class ObservationType(str, enum.Enum): SPAN = "SPAN" GENERATION = "GENERATION" EVENT = "EVENT" + AGENT = "AGENT" + TOOL = "TOOL" + CHAIN = "CHAIN" + RETRIEVER = "RETRIEVER" + EVALUATOR = "EVALUATOR" + EMBEDDING = "EMBEDDING" + GUARDRAIL = "GUARDRAIL" def visit( self, span: typing.Callable[[], T_Result], generation: typing.Callable[[], T_Result], event: typing.Callable[[], T_Result], + agent: typing.Callable[[], T_Result], + tool: typing.Callable[[], T_Result], + chain: typing.Callable[[], T_Result], + retriever: typing.Callable[[], T_Result], + evaluator: typing.Callable[[], T_Result], + embedding: typing.Callable[[], T_Result], + guardrail: typing.Callable[[], T_Result], ) -> T_Result: if self is ObservationType.SPAN: return span() @@ -23,3 +37,17 @@ def visit( return generation() if self is ObservationType.EVENT: return event() + if self is ObservationType.AGENT: + return agent() + if self is ObservationType.TOOL: + return tool() + if self is ObservationType.CHAIN: + return chain() + if self is ObservationType.RETRIEVER: + return retriever() + if self is ObservationType.EVALUATOR: + return evaluator() + if self is ObservationType.EMBEDDING: + return embedding() + if self is ObservationType.GUARDRAIL: + return guardrail() diff --git a/langfuse/api/resources/llm_connections/__init__.py b/langfuse/api/resources/llm_connections/__init__.py new file mode 100644 index 000000000..3cf778f1b --- /dev/null +++ b/langfuse/api/resources/llm_connections/__init__.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import ( + LlmAdapter, + LlmConnection, + PaginatedLlmConnections, + UpsertLlmConnectionRequest, +) + +__all__ = [ + "LlmAdapter", + "LlmConnection", + "PaginatedLlmConnections", + "UpsertLlmConnectionRequest", +] diff --git a/langfuse/api/resources/llm_connections/client.py b/langfuse/api/resources/llm_connections/client.py new file mode 100644 index 000000000..4497598c5 --- /dev/null +++ b/langfuse/api/resources/llm_connections/client.py @@ -0,0 +1,340 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from .types.llm_connection import LlmConnection +from .types.paginated_llm_connections import PaginatedLlmConnections +from .types.upsert_llm_connection_request import UpsertLlmConnectionRequest + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class LlmConnectionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedLlmConnections: + """ + Get all LLM connections in a project + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedLlmConnections + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.llm_connections.list() + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="GET", + params={"page": page, "limit": limit}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + PaginatedLlmConnections, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def upsert( + self, + *, + request: UpsertLlmConnectionRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> LlmConnection: + """ + Create or update an LLM connection. The connection is upserted on provider. + + Parameters + ---------- + request : UpsertLlmConnectionRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + LlmConnection + + Examples + -------- + from langfuse import LlmAdapter, UpsertLlmConnectionRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.llm_connections.upsert( + request=UpsertLlmConnectionRequest( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="PUT", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(LlmConnection, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncLlmConnectionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedLlmConnections: + """ + Get all LLM connections in a project + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedLlmConnections + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.llm_connections.list() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="GET", + params={"page": page, "limit": limit}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + PaginatedLlmConnections, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def upsert( + self, + *, + request: UpsertLlmConnectionRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> LlmConnection: + """ + Create or update an LLM connection. The connection is upserted on provider. + + Parameters + ---------- + request : UpsertLlmConnectionRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + LlmConnection + + Examples + -------- + import asyncio + + from langfuse import LlmAdapter, UpsertLlmConnectionRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.llm_connections.upsert( + request=UpsertLlmConnectionRequest( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="PUT", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(LlmConnection, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/llm_connections/types/__init__.py b/langfuse/api/resources/llm_connections/types/__init__.py new file mode 100644 index 000000000..b490e6e27 --- /dev/null +++ b/langfuse/api/resources/llm_connections/types/__init__.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from .llm_adapter import LlmAdapter +from .llm_connection import LlmConnection +from .paginated_llm_connections import PaginatedLlmConnections +from .upsert_llm_connection_request import UpsertLlmConnectionRequest + +__all__ = [ + "LlmAdapter", + "LlmConnection", + "PaginatedLlmConnections", + "UpsertLlmConnectionRequest", +] diff --git a/langfuse/api/resources/llm_connections/types/llm_adapter.py b/langfuse/api/resources/llm_connections/types/llm_adapter.py new file mode 100644 index 000000000..d03513aeb --- /dev/null +++ b/langfuse/api/resources/llm_connections/types/llm_adapter.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class LlmAdapter(str, enum.Enum): + ANTHROPIC = "anthropic" + OPEN_AI = "openai" + AZURE = "azure" + BEDROCK = "bedrock" + GOOGLE_VERTEX_AI = "google-vertex-ai" + GOOGLE_AI_STUDIO = "google-ai-studio" + + def visit( + self, + anthropic: typing.Callable[[], T_Result], + open_ai: typing.Callable[[], T_Result], + azure: typing.Callable[[], T_Result], + bedrock: typing.Callable[[], T_Result], + google_vertex_ai: typing.Callable[[], T_Result], + google_ai_studio: typing.Callable[[], T_Result], + ) -> T_Result: + if self is LlmAdapter.ANTHROPIC: + return anthropic() + if self is LlmAdapter.OPEN_AI: + return open_ai() + if self is LlmAdapter.AZURE: + return azure() + if self is LlmAdapter.BEDROCK: + return bedrock() + if self is LlmAdapter.GOOGLE_VERTEX_AI: + return google_vertex_ai() + if self is LlmAdapter.GOOGLE_AI_STUDIO: + return google_ai_studio() diff --git a/langfuse/api/resources/llm_connections/types/llm_connection.py b/langfuse/api/resources/llm_connections/types/llm_connection.py new file mode 100644 index 000000000..0b17b97a7 --- /dev/null +++ b/langfuse/api/resources/llm_connections/types/llm_connection.py @@ -0,0 +1,85 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class LlmConnection(pydantic_v1.BaseModel): + """ + LLM API connection configuration (secrets excluded) + """ + + id: str + provider: str = pydantic_v1.Field() + """ + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + """ + + adapter: str = pydantic_v1.Field() + """ + The adapter used to interface with the LLM + """ + + display_secret_key: str = pydantic_v1.Field(alias="displaySecretKey") + """ + Masked version of the secret key for display purposes + """ + + base_url: typing.Optional[str] = pydantic_v1.Field(alias="baseURL", default=None) + """ + Custom base URL for the LLM API + """ + + custom_models: typing.List[str] = pydantic_v1.Field(alias="customModels") + """ + List of custom model names available for this connection + """ + + with_default_models: bool = pydantic_v1.Field(alias="withDefaultModels") + """ + Whether to include default models for this adapter + """ + + extra_header_keys: typing.List[str] = pydantic_v1.Field(alias="extraHeaderKeys") + """ + Keys of extra headers sent with requests (values excluded for security) + """ + + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") + updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/llm_connections/types/paginated_llm_connections.py b/langfuse/api/resources/llm_connections/types/paginated_llm_connections.py new file mode 100644 index 000000000..986dbb0bb --- /dev/null +++ b/langfuse/api/resources/llm_connections/types/paginated_llm_connections.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from ...utils.resources.pagination.types.meta_response import MetaResponse +from .llm_connection import LlmConnection + + +class PaginatedLlmConnections(pydantic_v1.BaseModel): + data: typing.List[LlmConnection] + meta: MetaResponse + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py b/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py new file mode 100644 index 000000000..d0a5a368d --- /dev/null +++ b/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py @@ -0,0 +1,88 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .llm_adapter import LlmAdapter + + +class UpsertLlmConnectionRequest(pydantic_v1.BaseModel): + """ + Request to create or update an LLM connection (upsert) + """ + + provider: str = pydantic_v1.Field() + """ + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + """ + + adapter: LlmAdapter = pydantic_v1.Field() + """ + The adapter used to interface with the LLM + """ + + secret_key: str = pydantic_v1.Field(alias="secretKey") + """ + Secret key for the LLM API. + """ + + base_url: typing.Optional[str] = pydantic_v1.Field(alias="baseURL", default=None) + """ + Custom base URL for the LLM API + """ + + custom_models: typing.Optional[typing.List[str]] = pydantic_v1.Field( + alias="customModels", default=None + ) + """ + List of custom model names + """ + + with_default_models: typing.Optional[bool] = pydantic_v1.Field( + alias="withDefaultModels", default=None + ) + """ + Whether to include default models. Default is true. + """ + + extra_headers: typing.Optional[typing.Dict[str, str]] = pydantic_v1.Field( + alias="extraHeaders", default=None + ) + """ + Extra headers to send with requests + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations/client.py b/langfuse/api/resources/observations/client.py index 01bf60f78..b21981bb4 100644 --- a/langfuse/api/resources/observations/client.py +++ b/langfuse/api/resources/observations/client.py @@ -15,6 +15,7 @@ from ..commons.errors.method_not_allowed_error import MethodNotAllowedError from ..commons.errors.not_found_error import NotFoundError from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.observation_level import ObservationLevel from ..commons.types.observations_view import ObservationsView from .types.observations_views import ObservationsViews @@ -100,6 +101,7 @@ def get_many( user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, parent_observation_id: typing.Optional[str] = None, environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, from_start_time: typing.Optional[dt.datetime] = None, @@ -126,6 +128,9 @@ def get_many( trace_id : typing.Optional[str] + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + parent_observation_id : typing.Optional[str] environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -171,6 +176,7 @@ def get_many( "userId": user_id, "type": type, "traceId": trace_id, + "level": level, "parentObservationId": parent_observation_id, "environment": environment, "fromStartTime": serialize_datetime(from_start_time) @@ -299,6 +305,7 @@ async def get_many( user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, parent_observation_id: typing.Optional[str] = None, environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, from_start_time: typing.Optional[dt.datetime] = None, @@ -325,6 +332,9 @@ async def get_many( trace_id : typing.Optional[str] + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + parent_observation_id : typing.Optional[str] environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -378,6 +388,7 @@ async def main() -> None: "userId": user_id, "type": type, "traceId": trace_id, + "level": level, "parentObservationId": parent_observation_id, "environment": environment, "fromStartTime": serialize_datetime(from_start_time) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index ba2460c47..db90f10a0 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -3,7 +3,15 @@ import pydantic from langfuse._client.get_client import get_client -from langfuse._client.span import LangfuseGeneration, LangfuseSpan +from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.span import ( + LangfuseGeneration, + LangfuseSpan, + LangfuseAgent, + LangfuseChain, + LangfuseTool, + LangfuseRetriever, +) from langfuse.logger import langfuse_logger try: @@ -67,7 +75,17 @@ def __init__( """ self.client = get_client(public_key=public_key) - self.runs: Dict[UUID, Union[LangfuseSpan, LangfuseGeneration]] = {} + self.runs: Dict[ + UUID, + Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseChain, + LangfuseTool, + LangfuseRetriever, + ], + ] = {} self.prompt_to_parent_run_map: Dict[UUID, Any] = {} self.updated_completion_start_time_memo: Set[UUID] = set() @@ -96,6 +114,49 @@ def on_llm_new_token( self.updated_completion_start_time_memo.add(run_id) + def _get_observation_type_from_serialized( + self, serialized: Optional[Dict[str, Any]], callback_type: str, **kwargs: Any + ) -> Union[ + Literal["tool"], + Literal["retriever"], + Literal["generation"], + Literal["agent"], + Literal["chain"], + Literal["span"], + ]: + """Determine Langfuse observation type from LangChain component. + + Args: + serialized: LangChain's serialized component dict + callback_type: The type of callback (e.g., "chain", "tool", "retriever", "llm") + **kwargs: Additional keyword arguments from the callback + + Returns: + The appropriate Langfuse observation type string + """ + # Direct mappings based on callback type + if callback_type == "tool": + return "tool" + elif callback_type == "retriever": + return "retriever" + elif callback_type == "llm": + return "generation" + elif callback_type == "chain": + # Detect if it's an agent by examining class path or name + if serialized and "id" in serialized: + class_path = serialized["id"] + if any("agent" in part.lower() for part in class_path): + return "agent" + + # Check name for agent-related keywords + name = self.get_langchain_run_name(serialized, **kwargs) + if "agent" in name.lower(): + return "agent" + + return "chain" + + return "span" + def get_langchain_run_name( self, serialized: Optional[Dict[str, Any]], **kwargs: Any ) -> str: @@ -205,9 +266,14 @@ def on_chain_start( span_metadata = self.__join_tags_and_metadata(tags, metadata) span_level = "DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None + observation_type = self._get_observation_type_from_serialized( + serialized, "chain", **kwargs + ) + if parent_run_id is None: - span = self.client.start_span( + span = self.client.start_observation( name=span_name, + as_type=observation_type, metadata=span_metadata, input=inputs, level=cast( @@ -233,9 +299,11 @@ def on_chain_start( self.runs[run_id] = span else: self.runs[run_id] = cast( - LangfuseSpan, self.runs[parent_run_id] - ).start_span( + LangfuseChain, + self.runs[parent_run_id], + ).start_observation( name=span_name, + as_type=observation_type, metadata=span_metadata, input=inputs, level=cast( @@ -296,7 +364,13 @@ def on_agent_action( if run_id not in self.runs: raise Exception("run not found") - self.runs[run_id].update( + agent_run = self.runs[run_id] + if hasattr(agent_run, "_otel_span"): + agent_run._otel_span.set_attribute( + LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "agent" + ) + + agent_run.update( output=action, input=kwargs.get("inputs"), ).end() @@ -319,7 +393,13 @@ def on_agent_finish( if run_id not in self.runs: raise Exception("run not found") - self.runs[run_id].update( + agent_run = self.runs[run_id] + if hasattr(agent_run, "_otel_span"): + agent_run._otel_span.set_attribute( + LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "agent" + ) + + agent_run.update( output=finish, input=kwargs.get("inputs"), ).end() @@ -470,8 +550,6 @@ def on_tool_start( "on_tool_start", run_id, parent_run_id, input_str=input_str ) - if parent_run_id is None or parent_run_id not in self.runs: - raise Exception("parent run not found") meta = self.__join_tags_and_metadata(tags, metadata) if not meta: @@ -481,13 +559,31 @@ def on_tool_start( {key: value for key, value in kwargs.items() if value is not None} ) - self.runs[run_id] = cast(LangfuseSpan, self.runs[parent_run_id]).start_span( - name=self.get_langchain_run_name(serialized, **kwargs), - input=input_str, - metadata=meta, - level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, + observation_type = self._get_observation_type_from_serialized( + serialized, "tool", **kwargs ) + if parent_run_id is None or parent_run_id not in self.runs: + # Create root observation for direct tool calls + self.runs[run_id] = self.client.start_observation( + name=self.get_langchain_run_name(serialized, **kwargs), + as_type=observation_type, + input=input_str, + metadata=meta, + level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, + ) + else: + # Create child observation for tools within chains/agents + self.runs[run_id] = cast( + LangfuseChain, self.runs[parent_run_id] + ).start_observation( + name=self.get_langchain_run_name(serialized, **kwargs), + as_type=observation_type, + input=input_str, + metadata=meta, + level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, + ) + except Exception as e: langfuse_logger.exception(e) @@ -510,9 +606,14 @@ def on_retriever_start( span_metadata = self.__join_tags_and_metadata(tags, metadata) span_level = "DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None + observation_type = self._get_observation_type_from_serialized( + serialized, "retriever", **kwargs + ) + if parent_run_id is None: - self.runs[run_id] = self.client.start_span( + self.runs[run_id] = self.client.start_observation( name=span_name, + as_type=observation_type, metadata=span_metadata, input=query, level=cast( @@ -522,9 +623,10 @@ def on_retriever_start( ) else: self.runs[run_id] = cast( - LangfuseSpan, self.runs[parent_run_id] - ).start_span( + LangfuseRetriever, self.runs[parent_run_id] + ).start_observation( name=span_name, + as_type=observation_type, input=query, metadata=span_metadata, level=cast( @@ -653,10 +755,12 @@ def __on_llm_action( if parent_run_id is not None and parent_run_id in self.runs: self.runs[run_id] = cast( - LangfuseSpan, self.runs[parent_run_id] - ).start_generation(**content) # type: ignore + LangfuseGeneration, self.runs[parent_run_id] + ).start_observation(as_type="generation", **content) # type: ignore else: - self.runs[run_id] = self.client.start_generation(**content) # type: ignore + self.runs[run_id] = self.client.start_observation( + as_type="generation", **content + ) # type: ignore self.last_trace_id = self.runs[run_id].trace_id diff --git a/langfuse/openai.py b/langfuse/openai.py index d8265044b..5f163db48 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -740,7 +740,8 @@ def _wrap( langfuse_data = _get_langfuse_data_from_kwargs(open_ai_resource, langfuse_args) langfuse_client = get_client(public_key=langfuse_args["langfuse_public_key"]) - generation = langfuse_client.start_generation( + generation = langfuse_client.start_observation( + as_type="generation", name=langfuse_data["name"], input=langfuse_data.get("input", None), metadata=langfuse_data.get("metadata", None), @@ -803,7 +804,8 @@ async def _wrap_async( langfuse_data = _get_langfuse_data_from_kwargs(open_ai_resource, langfuse_args) langfuse_client = get_client(public_key=langfuse_args["langfuse_public_key"]) - generation = langfuse_client.start_generation( + generation = langfuse_client.start_observation( + as_type="generation", name=langfuse_data["name"], input=langfuse_data.get("input", None), metadata=langfuse_data.get("metadata", None), diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 9d1acae85..9a758e38a 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1878,3 +1878,145 @@ def test_generate_trace_id(): project_id = langfuse._get_project_id() trace_url = langfuse.get_trace_url(trace_id=trace_id) assert trace_url == f"http://localhost:3000/project/{project_id}/traces/{trace_id}" + + +def test_start_as_current_observation_types(): + """Test creating different observation types using start_as_current_observation.""" + langfuse = Langfuse() + + observation_types = [ + "span", + "generation", + "agent", + "tool", + "chain", + "retriever", + "evaluator", + "embedding", + "guardrail", + ] + + with langfuse.start_as_current_span(name="parent") as parent_span: + parent_span.update_trace(name="observation-types-test") + trace_id = parent_span.trace_id + + for obs_type in observation_types: + with parent_span.start_as_current_observation( + name=f"test-{obs_type}", as_type=obs_type + ): + pass + + langfuse.flush() + sleep(2) + + api = get_api() + trace = api.trace.get(trace_id) + + # Check we have all expected observation types + found_types = {obs.type for obs in trace.observations} + expected_types = {obs_type.upper() for obs_type in observation_types} | { + "SPAN" + } # includes parent span + assert expected_types.issubset( + found_types + ), f"Missing types: {expected_types - found_types}" + + # Verify each specific observation exists + for obs_type in observation_types: + observations = [ + obs + for obs in trace.observations + if obs.name == f"test-{obs_type}" and obs.type == obs_type.upper() + ] + assert len(observations) == 1, f"Expected one {obs_type.upper()} observation" + + +def test_that_generation_like_properties_are_actually_created(): + """Test that generation-like observation types properly support generation properties.""" + from langfuse._client.constants import ( + get_observation_types_list, + ObservationTypeGenerationLike, + ) + + langfuse = Langfuse() + generation_like_types = get_observation_types_list(ObservationTypeGenerationLike) + + test_model = "test-model" + test_completion_start_time = datetime.now(timezone.utc) + test_model_parameters = {"temperature": "0.7", "max_tokens": "100"} + test_usage_details = {"prompt_tokens": 10, "completion_tokens": 20} + test_cost_details = {"input": 0.01, "output": 0.02, "total": 0.03} + + with langfuse.start_as_current_span(name="parent") as parent_span: + parent_span.update_trace(name="generation-properties-test") + trace_id = parent_span.trace_id + + for obs_type in generation_like_types: + with parent_span.start_as_current_observation( + name=f"test-{obs_type}", + as_type=obs_type, + model=test_model, + completion_start_time=test_completion_start_time, + model_parameters=test_model_parameters, + usage_details=test_usage_details, + cost_details=test_cost_details, + ) as obs: + # Verify the properties are accessible on the observation object + if hasattr(obs, "model"): + assert ( + obs.model == test_model + ), f"{obs_type} should have model property" + if hasattr(obs, "completion_start_time"): + assert ( + obs.completion_start_time == test_completion_start_time + ), f"{obs_type} should have completion_start_time property" + if hasattr(obs, "model_parameters"): + assert ( + obs.model_parameters == test_model_parameters + ), f"{obs_type} should have model_parameters property" + if hasattr(obs, "usage_details"): + assert ( + obs.usage_details == test_usage_details + ), f"{obs_type} should have usage_details property" + if hasattr(obs, "cost_details"): + assert ( + obs.cost_details == test_cost_details + ), f"{obs_type} should have cost_details property" + + langfuse.flush() + + api = get_api() + trace = api.trace.get(trace_id) + + # Verify that the properties are persisted in the API for generation-like types + for obs_type in generation_like_types: + observations = [ + obs + for obs in trace.observations + if obs.name == f"test-{obs_type}" and obs.type == obs_type.upper() + ] + assert ( + len(observations) == 1 + ), f"Expected one {obs_type.upper()} observation, but found {len(observations)}" + + obs = observations[0] + + assert obs.model == test_model, f"{obs_type} should have model property" + assert ( + obs.model_parameters == test_model_parameters + ), f"{obs_type} should have model_parameters property" + + # usage_details + assert hasattr(obs, "usage_details"), f"{obs_type} should have usage_details" + assert obs.usage_details == dict( + test_usage_details, total=30 + ), f"{obs_type} should persist usage_details" # API adds total + + assert ( + obs.cost_details == test_cost_details + ), f"{obs_type} should persist cost_details" + + # completion_start_time, because of time skew not asserting time + assert ( + obs.completion_start_time is not None + ), f"{obs_type} should persist completion_start_time property" diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 535625918..7217c0a8d 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -319,7 +319,7 @@ def sorted_dependencies_from_trace(trace): if len(sorted_observations) >= 2: assert sorted_observations[1].name == "RunnableSequence" - assert sorted_observations[1].type == "SPAN" + assert sorted_observations[1].type == "CHAIN" assert sorted_observations[1].input is not None assert sorted_observations[1].output is not None assert sorted_observations[1].input != "" diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py new file mode 100644 index 000000000..9877f97d1 --- /dev/null +++ b/tests/test_deprecation.py @@ -0,0 +1,119 @@ +"""Tests for deprecation warnings on deprecated functions.""" + +import warnings +import pytest +from unittest.mock import patch + +from langfuse import Langfuse + + +class TestDeprecationWarnings: + """Test that deprecated functions emit proper deprecation warnings.""" + + # List of deprecated functions and their expected warning messages. Target is the object they are called on. + DEPRECATED_FUNCTIONS = [ + # on the client: + { + "method": "start_generation", + "target": "client", + "kwargs": {"name": "test_generation"}, + "expected_message": "start_generation is deprecated and will be removed in a future version. Use start_observation(as_type='generation') instead.", + }, + { + "method": "start_as_current_generation", + "target": "client", + "kwargs": {"name": "test_generation"}, + "expected_message": "start_as_current_generation is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='generation') instead.", + }, + # on the span: + { + "method": "start_generation", + "target": "span", + "kwargs": {"name": "test_generation"}, + "expected_message": "start_generation is deprecated and will be removed in a future version. Use start_observation(as_type='generation') instead.", + }, + { + "method": "start_as_current_generation", + "target": "span", + "kwargs": {"name": "test_generation"}, + "expected_message": "start_as_current_generation is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='generation') instead.", + }, + { + "method": "start_as_current_span", + "target": "span", + "kwargs": {"name": "test_span"}, + "expected_message": "start_as_current_span is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='span') instead.", + }, + ] + + @pytest.fixture + def langfuse_client(self): + """Create a Langfuse client for testing.""" + with patch.dict( + "os.environ", + { + "LANGFUSE_PUBLIC_KEY": "test_key", + "LANGFUSE_SECRET_KEY": "test_secret", + "LANGFUSE_HOST": "http://localhost:3000", + }, + ): + return Langfuse() + + @pytest.mark.parametrize("func_info", DEPRECATED_FUNCTIONS) + def test_deprecated_function_warnings(self, langfuse_client, func_info): + """Test that deprecated functions emit proper deprecation warnings.""" + method_name = func_info["method"] + target = func_info["target"] + kwargs = func_info["kwargs"] + expected_message = func_info["expected_message"] + + with warnings.catch_warnings(record=True) as warning_list: + warnings.simplefilter("always") + + try: + if target == "client": + # Test deprecated methods on the client + method = getattr(langfuse_client, method_name) + if "current" in method_name: + # Context manager methods + with method(**kwargs) as obj: + if hasattr(obj, "end"): + obj.end() + else: + # Regular methods + obj = method(**kwargs) + if hasattr(obj, "end"): + obj.end() + + elif target == "span": + # Test deprecated methods on spans + span = langfuse_client.start_span(name="test_parent") + method = getattr(span, method_name) + if "current" in method_name: + # Context manager methods + with method(**kwargs) as obj: + if hasattr(obj, "end"): + obj.end() + else: + # Regular methods + obj = method(**kwargs) + if hasattr(obj, "end"): + obj.end() + span.end() + + except Exception: + pass + + # Check that a deprecation warning was emitted + deprecation_warnings = [ + w for w in warning_list if issubclass(w.category, DeprecationWarning) + ] + assert ( + len(deprecation_warnings) > 0 + ), f"No DeprecationWarning emitted for {target}.{method_name}" + + # Check that the warning message matches expected + warning_messages = [str(w.message) for w in deprecation_warnings] + assert ( + expected_message in warning_messages + ), f"Expected warning message not found for {target}.{method_name}. Got: {warning_messages}" diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 71b0cb5f1..4e4093e38 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -60,7 +60,7 @@ def test_callback_generated_from_trace_chain(): langchain_span = list( filter( - lambda o: o.type == "SPAN" and o.name == "LLMChain", + lambda o: o.type == "CHAIN" and o.name == "LLMChain", trace.observations, ) )[0] @@ -458,11 +458,11 @@ def test_agent_executor_chain(): prompt = PromptTemplate.from_template(""" Answer the following questions as best you can. You have access to the following tools: - + {tools} - + Use the following format: - + Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] @@ -471,9 +471,9 @@ def test_agent_executor_chain(): ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question - + Begin! - + Question: {input} Thought:{agent_scratchpad} """) @@ -558,7 +558,7 @@ def _identifying_params(self) -> Mapping[str, Any]: template = """You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play. - + Play Synopsis: {synopsis} Review from a New York Times play critic of the above play:""" @@ -604,9 +604,9 @@ def test_openai_instruct_usage(): runnable_chain: Runnable = ( PromptTemplate.from_template( """Answer the question based only on the following context: - + Question: {question} - + Answer in the following language: {language} """ ) @@ -1353,3 +1353,92 @@ def test_cached_token_usage(): ) < 0.0001 ) + + +def test_langchain_automatic_observation_types(): + """Test that LangChain components automatically get correct observation types: + AGENT, TOOL, GENERATION, RETRIEVER, CHAIN + """ + langfuse = Langfuse() + + with langfuse.start_as_current_span(name="observation_types_test_agent") as span: + trace_id = span.trace_id + handler = CallbackHandler() + + from langchain.agents import AgentExecutor, create_react_agent + from langchain.tools import tool + + # for type TOOL + @tool + def test_tool(x: str) -> str: + """Process input string.""" + return f"processed {x}" + + # for type GENERATION + llm = ChatOpenAI(temperature=0) + tools = [test_tool] + + prompt = PromptTemplate.from_template(""" + Answer: {input} + + Tools: {tools} + Tool names: {tool_names} + + Question: {input} + {agent_scratchpad} + """) + + # for type AGENT + agent = create_react_agent(llm, tools, prompt) + agent_executor = AgentExecutor( + agent=agent, tools=tools, handle_parsing_errors=True, max_iterations=1 + ) + + try: + agent_executor.invoke({"input": "hello"}, {"callbacks": [handler]}) + except Exception: + pass + + try: + test_tool.invoke("simple input", {"callbacks": [handler]}) + except Exception: + pass + + from langchain_core.prompts import PromptTemplate as CorePromptTemplate + + # for type CHAIN + chain_prompt = CorePromptTemplate.from_template("Answer: {question}") + simple_chain = chain_prompt | llm + + try: + simple_chain.invoke({"question": "hi"}, {"callbacks": [handler]}) + except Exception: + pass + + # for type RETRIEVER + from langchain_core.retrievers import BaseRetriever + from langchain_core.documents import Document + + class SimpleRetriever(BaseRetriever): + def _get_relevant_documents(self, query: str, *, run_manager): + return [Document(page_content="test doc")] + + try: + SimpleRetriever().invoke("query", {"callbacks": [handler]}) + except Exception: + pass + + handler.client.flush() + trace = get_api().trace.get(trace_id) + + # Validate all expected observation types are created + types_found = {obs.type for obs in trace.observations} + expected_types = {"AGENT", "TOOL", "CHAIN", "RETRIEVER", "GENERATION"} + + for obs_type in expected_types: + obs_count = len([obs for obs in trace.observations if obs.type == obs_type]) + assert obs_count > 0, f"Expected {obs_type} observations, found {obs_count}" + + assert expected_types.issubset( + types_found + ), f"Missing types: {expected_types - types_found}" diff --git a/tests/test_otel.py b/tests/test_otel.py index dfa298161..fd29ce671 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -102,7 +102,6 @@ def mock_init(self, **kwargs): @pytest.fixture def langfuse_client(self, monkeypatch, tracer_provider, mock_processor_init): """Create a mocked Langfuse client for testing.""" - # Set environment variables monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "test-public-key") monkeypatch.setenv("LANGFUSE_SECRET_KEY", "test-secret-key") @@ -589,6 +588,184 @@ def test_update_current_generation_name(self, langfuse_client, memory_exporter): ) assert len(original_spans) == 0, "Expected no generations with original name" + def test_start_as_current_observation_types(self, langfuse_client, memory_exporter): + """Test creating different observation types using start_as_current_observation.""" + # Test each observation type from ObservationTypeLiteralNoEvent + observation_types = [ + "span", + "generation", + "agent", + "tool", + "chain", + "retriever", + "evaluator", + "embedding", + "guardrail", + ] + + for obs_type in observation_types: + with langfuse_client.start_as_current_observation( + name=f"test-{obs_type}", as_type=obs_type + ) as obs: + obs.update_trace(name=f"trace-{obs_type}") + + spans = [ + self.get_span_data(span) for span in memory_exporter.get_finished_spans() + ] + + # Find spans by name and verify their observation types + for obs_type in observation_types: + expected_name = f"test-{obs_type}" + matching_spans = [span for span in spans if span["name"] == expected_name] + assert ( + len(matching_spans) == 1 + ), f"Expected one span with name {expected_name}" + + span_data = matching_spans[0] + expected_otel_type = obs_type # OTEL attributes use lowercase + actual_type = span_data["attributes"].get( + LangfuseOtelSpanAttributes.OBSERVATION_TYPE + ) + + assert ( + actual_type == expected_otel_type + ), f"Expected observation type {expected_otel_type}, got {actual_type}" + + def test_start_observation(self, langfuse_client, memory_exporter): + """Test creating different observation types using start_observation.""" + from langfuse._client.constants import ( + ObservationTypeGenerationLike, + ObservationTypeLiteral, + get_observation_types_list, + ) + + # Test each observation type defined in constants - this ensures we test all supported types + observation_types = get_observation_types_list(ObservationTypeLiteral) + + # Create a main span to use for child creation + with langfuse_client.start_as_current_span( + name="factory-test-parent" + ) as parent_span: + created_observations = [] + + for obs_type in observation_types: + if obs_type in get_observation_types_list( + ObservationTypeGenerationLike + ): + # Generation-like types with extra parameters + obs = parent_span.start_observation( + name=f"factory-{obs_type}", + as_type=obs_type, + input={"test": f"{obs_type}_input"}, + model="test-model", + model_parameters={"temperature": 0.7}, + usage_details={"input": 10, "output": 20}, + ) + if obs_type != "event": # Events are auto-ended + obs.end() + created_observations.append((obs_type, obs)) + elif obs_type == "event": + # Test event creation through start_observation (should be auto-ended) + obs = parent_span.start_observation( + name=f"factory-{obs_type}", + as_type=obs_type, + input={"test": f"{obs_type}_input"}, + ) + created_observations.append((obs_type, obs)) + else: + # Span-like types (span, guardrail) + obs = parent_span.start_observation( + name=f"factory-{obs_type}", + as_type=obs_type, + input={"test": f"{obs_type}_input"}, + ) + obs.end() + created_observations.append((obs_type, obs)) + + spans = [ + self.get_span_data(span) for span in memory_exporter.get_finished_spans() + ] + + # Verify factory pattern created correct observation types + for obs_type in observation_types: + expected_name = f"factory-{obs_type}" + matching_spans = [span for span in spans if span["name"] == expected_name] + assert ( + len(matching_spans) == 1 + ), f"Expected one span with name {expected_name}, found {len(matching_spans)}" + + span_data = matching_spans[0] + actual_type = span_data["attributes"].get( + LangfuseOtelSpanAttributes.OBSERVATION_TYPE + ) + + assert ( + actual_type == obs_type + ), f"Factory pattern failed: Expected observation type {obs_type}, got {actual_type}" + + # Ensure returned objects are of correct types + for obs_type, obs_instance in created_observations: + if obs_type == "span": + from langfuse._client.span import LangfuseSpan + + assert isinstance( + obs_instance, LangfuseSpan + ), f"Expected LangfuseSpan, got {type(obs_instance)}" + elif obs_type == "generation": + from langfuse._client.span import LangfuseGeneration + + assert isinstance( + obs_instance, LangfuseGeneration + ), f"Expected LangfuseGeneration, got {type(obs_instance)}" + elif obs_type == "agent": + from langfuse._client.span import LangfuseAgent + + assert isinstance( + obs_instance, LangfuseAgent + ), f"Expected LangfuseAgent, got {type(obs_instance)}" + elif obs_type == "tool": + from langfuse._client.span import LangfuseTool + + assert isinstance( + obs_instance, LangfuseTool + ), f"Expected LangfuseTool, got {type(obs_instance)}" + elif obs_type == "chain": + from langfuse._client.span import LangfuseChain + + assert isinstance( + obs_instance, LangfuseChain + ), f"Expected LangfuseChain, got {type(obs_instance)}" + elif obs_type == "retriever": + from langfuse._client.span import LangfuseRetriever + + assert isinstance( + obs_instance, LangfuseRetriever + ), f"Expected LangfuseRetriever, got {type(obs_instance)}" + elif obs_type == "evaluator": + from langfuse._client.span import LangfuseEvaluator + + assert isinstance( + obs_instance, LangfuseEvaluator + ), f"Expected LangfuseEvaluator, got {type(obs_instance)}" + elif obs_type == "embedding": + from langfuse._client.span import LangfuseEmbedding + + assert isinstance( + obs_instance, LangfuseEmbedding + ), f"Expected LangfuseEmbedding, got {type(obs_instance)}" + elif obs_type == "guardrail": + from langfuse._client.span import LangfuseGuardrail + + assert isinstance( + obs_instance, LangfuseGuardrail + ), f"Expected LangfuseGuardrail, got {type(obs_instance)}" + elif obs_type == "event": + from langfuse._client.span import LangfuseEvent + + assert isinstance( + obs_instance, LangfuseEvent + ), f"Expected LangfuseEvent, got {type(obs_instance)}" + def test_custom_trace_id(self, langfuse_client, memory_exporter): """Test setting a custom trace ID.""" # Create a custom trace ID @@ -2852,3 +3029,33 @@ def test_different_seeds_produce_different_ids(self, langfuse_client): # All observation IDs should be unique assert len(set(observation_ids)) == len(seeds) + + def test_langfuse_event_update_immutability(self, langfuse_client, caplog): + """Test that LangfuseEvent.update() logs a warning and does nothing.""" + import logging + + parent_span = langfuse_client.start_span(name="parent-span") + + event = parent_span.start_observation( + name="test-event", + as_type="event", + input={"original": "input"}, + ) + + # Try to update the event and capture warning logs + with caplog.at_level(logging.WARNING, logger="langfuse._client.span"): + result = event.update( + name="updated_name", + input={"updated": "input"}, + output={"updated": "output"}, + metadata={"updated": "metadata"}, + ) + + # Verify warning was logged + assert "Attempted to update LangfuseEvent observation" in caplog.text + assert "Events cannot be updated after creation" in caplog.text + + # Verify the method returned self unchanged + assert result is event + + parent_span.end() From 90472537b28e34ce00f8e09ca253594d99e54e9c Mon Sep 17 00:00:00 2001 From: qnnn <1543393961@qq.com> Date: Mon, 25 Aug 2025 16:27:03 +0800 Subject: [PATCH 024/296] chore: update default value docs for `FLUSH_AT` and `FLUSH_INTERVAL` (#1306) update default value docs for `FLUSH_AT` and `FLUSH_INTERVAL` Signed-off-by: qnnn --- langfuse/_client/environment_variables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/environment_variables.py b/langfuse/_client/environment_variables.py index b868b1e24..4394d2077 100644 --- a/langfuse/_client/environment_variables.py +++ b/langfuse/_client/environment_variables.py @@ -76,7 +76,7 @@ .. envvar:: LANGFUSE_FLUSH_AT Max batch size until a new ingestion batch is sent to the API. -**Default value:** ``15`` +**Default value:** same as OTEL ``OTEL_BSP_MAX_EXPORT_BATCH_SIZE`` """ LANGFUSE_FLUSH_INTERVAL = "LANGFUSE_FLUSH_INTERVAL" @@ -84,7 +84,7 @@ .. envvar:: LANGFUSE_FLUSH_INTERVAL Max delay in seconds until a new ingestion batch is sent to the API. -**Default value:** ``1`` +**Default value:** same as OTEL ``OTEL_BSP_SCHEDULE_DELAY`` """ LANGFUSE_SAMPLE_RATE = "LANGFUSE_SAMPLE_RATE" From df5b72acac091155af0824d00907f4a8ca3d4eb3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:53:49 +0200 Subject: [PATCH 025/296] fix(langchain): capture usage on streamed gemini responses (#1309) --- langfuse/langchain/CallbackHandler.py | 4 +++- tests/test_langchain.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index db90f10a0..b3cc76df4 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1140,7 +1140,9 @@ def _parse_usage(response: LLMResult) -> Any: llm_usage = _parse_usage_model( generation_chunk.generation_info["usage_metadata"] ) - break + + if llm_usage is not None: + break message_chunk = getattr(generation_chunk, "message", {}) response_metadata = getattr(message_chunk, "response_metadata", {}) diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 4e4093e38..0a3ac72f1 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -177,6 +177,7 @@ def test_callback_generated_from_lcel_chain(): assert langchain_generation_span.output != "" +@pytest.mark.skip(reason="Flaky") def test_basic_chat_openai(): # Create a unique name for this test test_name = f"Test Basic Chat {create_uuid()}" From 64378a29b6064a83412f3567661c7df5e8f39061 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:54:07 +0200 Subject: [PATCH 026/296] chore: release v3.3.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 257d59df6..6bc71012d 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.3.0" +__version__ = "3.3.1" diff --git a/pyproject.toml b/pyproject.toml index ece5e120e..54c5ee0a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.3.0" +version = "3.3.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From b5505b2769749fa3b86ff5b61ebf40b5593537ab Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:04:17 +0200 Subject: [PATCH 027/296] feat(langchain): set callback spans to active in OTEL context (#1310) --- langfuse/langchain/CallbackHandler.py | 313 +++++++++++++------------- 1 file changed, 158 insertions(+), 155 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index b3cc76df4..ed8046e8e 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1,16 +1,18 @@ import typing +from contextvars import Token import pydantic +from opentelemetry import context, trace -from langfuse._client.get_client import get_client from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.get_client import get_client from langfuse._client.span import ( - LangfuseGeneration, - LangfuseSpan, LangfuseAgent, LangfuseChain, - LangfuseTool, + LangfuseGeneration, LangfuseRetriever, + LangfuseSpan, + LangfuseTool, ) from langfuse.logger import langfuse_logger @@ -86,6 +88,7 @@ def __init__( LangfuseRetriever, ], ] = {} + self.context_tokens: Dict[UUID, Token] = {} self.prompt_to_parent_run_map: Dict[UUID, Any] = {} self.updated_completion_start_time_memo: Set[UUID] = set() @@ -210,11 +213,14 @@ def on_retriever_error( if run_id is None or run_id not in self.runs: raise Exception("run not found") - self.runs[run_id].update( - level="ERROR", - status_message=str(error), - input=kwargs.get("inputs"), - ).end() + observation = self._detach_observation(run_id) + + if observation is not None: + observation.update( + level="ERROR", + status_message=str(error), + input=kwargs.get("inputs"), + ).end() except Exception as e: langfuse_logger.exception(e) @@ -270,17 +276,19 @@ def on_chain_start( serialized, "chain", **kwargs ) + span = self.client.start_observation( + name=span_name, + as_type=observation_type, + metadata=span_metadata, + input=inputs, + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + span_level, + ), + ) + self._attach_observation(run_id, span) + if parent_run_id is None: - span = self.client.start_observation( - name=span_name, - as_type=observation_type, - metadata=span_metadata, - input=inputs, - level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], - span_level, - ), - ) span.update_trace( **( cast( @@ -296,21 +304,6 @@ def on_chain_start( ), **self._parse_langfuse_trace_attributes_from_metadata(metadata), ) - self.runs[run_id] = span - else: - self.runs[run_id] = cast( - LangfuseChain, - self.runs[parent_run_id], - ).start_observation( - name=span_name, - as_type=observation_type, - metadata=span_metadata, - input=inputs, - level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], - span_level, - ), - ) self.last_trace_id = self.runs[run_id].trace_id @@ -347,6 +340,53 @@ def _deregister_langfuse_prompt(self, run_id: Optional[UUID]) -> None: if run_id is not None and run_id in self.prompt_to_parent_run_map: del self.prompt_to_parent_run_map[run_id] + def _attach_observation( + self, + run_id: UUID, + observation: Union[ + LangfuseAgent, + LangfuseChain, + LangfuseGeneration, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, + ], + ) -> None: + ctx = trace.set_span_in_context(observation._otel_span) + token = context.attach(ctx) + + self.runs[run_id] = observation + self.context_tokens[run_id] = token + + def _detach_observation( + self, run_id: UUID + ) -> Optional[ + Union[ + LangfuseAgent, + LangfuseChain, + LangfuseGeneration, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, + ] + ]: + token = self.context_tokens.pop(run_id, None) + + if token: + context.detach(token) + + return cast( + Union[ + LangfuseAgent, + LangfuseChain, + LangfuseGeneration, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, + ], + self.runs.pop(run_id, None), + ) + def on_agent_action( self, action: AgentAction, @@ -393,16 +433,17 @@ def on_agent_finish( if run_id not in self.runs: raise Exception("run not found") - agent_run = self.runs[run_id] - if hasattr(agent_run, "_otel_span"): + agent_run = self._detach_observation(run_id) + + if agent_run is not None: agent_run._otel_span.set_attribute( LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "agent" ) - agent_run.update( - output=finish, - input=kwargs.get("inputs"), - ).end() + agent_run.update( + output=finish, + input=kwargs.get("inputs"), + ).end() except Exception as e: langfuse_logger.exception(e) @@ -423,20 +464,20 @@ def on_chain_end( if run_id not in self.runs: raise Exception("run not found") - span = self.runs[run_id] - span.update( - output=outputs, - input=kwargs.get("inputs"), - ) + span = self._detach_observation(run_id) - if parent_run_id is None and self.update_trace: - span.update_trace(output=outputs, input=kwargs.get("inputs")) + if span is not None: + span.update( + output=outputs, + input=kwargs.get("inputs"), + ) - span.end() + if parent_run_id is None and self.update_trace: + span.update_trace(output=outputs, input=kwargs.get("inputs")) - del self.runs[run_id] + span.end() - self._deregister_langfuse_prompt(run_id) + self._deregister_langfuse_prompt(run_id) except Exception as e: langfuse_logger.exception(e) @@ -452,26 +493,23 @@ def on_chain_error( ) -> None: try: self._log_debug_event("on_chain_error", run_id, parent_run_id, error=error) - if run_id in self.runs: - if any(isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES): - level = None - else: - level = "ERROR" + if any(isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES): + level = None + else: + level = "ERROR" + + observation = self._detach_observation(run_id) - self.runs[run_id].update( + if observation is not None: + observation.update( level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], level + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + level, ), status_message=str(error) if level else None, input=kwargs.get("inputs"), ).end() - del self.runs[run_id] - else: - langfuse_logger.warning( - f"Run ID {run_id} already popped from run map. Could not update run with error message" - ) - except Exception as e: langfuse_logger.exception(e) @@ -563,26 +601,15 @@ def on_tool_start( serialized, "tool", **kwargs ) - if parent_run_id is None or parent_run_id not in self.runs: - # Create root observation for direct tool calls - self.runs[run_id] = self.client.start_observation( - name=self.get_langchain_run_name(serialized, **kwargs), - as_type=observation_type, - input=input_str, - metadata=meta, - level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, - ) - else: - # Create child observation for tools within chains/agents - self.runs[run_id] = cast( - LangfuseChain, self.runs[parent_run_id] - ).start_observation( - name=self.get_langchain_run_name(serialized, **kwargs), - as_type=observation_type, - input=input_str, - metadata=meta, - level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, - ) + span = self.client.start_observation( + name=self.get_langchain_run_name(serialized, **kwargs), + as_type=observation_type, + input=input_str, + metadata=meta, + level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, + ) + + self._attach_observation(run_id, span) except Exception as e: langfuse_logger.exception(e) @@ -610,30 +637,18 @@ def on_retriever_start( serialized, "retriever", **kwargs ) - if parent_run_id is None: - self.runs[run_id] = self.client.start_observation( - name=span_name, - as_type=observation_type, - metadata=span_metadata, - input=query, - level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], - span_level, - ), - ) - else: - self.runs[run_id] = cast( - LangfuseRetriever, self.runs[parent_run_id] - ).start_observation( - name=span_name, - as_type=observation_type, - input=query, - metadata=span_metadata, - level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], - span_level, - ), - ) + span = self.client.start_observation( + name=span_name, + as_type=observation_type, + metadata=span_metadata, + input=query, + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + span_level, + ), + ) + + self._attach_observation(run_id, span) except Exception as e: langfuse_logger.exception(e) @@ -650,15 +665,13 @@ def on_retriever_end( self._log_debug_event( "on_retriever_end", run_id, parent_run_id, documents=documents ) - if run_id is None or run_id not in self.runs: - raise Exception("run not found") + observation = self._detach_observation(run_id) - self.runs[run_id].update( - output=documents, - input=kwargs.get("inputs"), - ).end() - - del self.runs[run_id] + if observation is not None: + observation.update( + output=documents, + input=kwargs.get("inputs"), + ).end() except Exception as e: langfuse_logger.exception(e) @@ -673,15 +686,14 @@ def on_tool_end( ) -> Any: try: self._log_debug_event("on_tool_end", run_id, parent_run_id, output=output) - if run_id is None or run_id not in self.runs: - raise Exception("run not found") - self.runs[run_id].update( - output=output, - input=kwargs.get("inputs"), - ).end() + observation = self._detach_observation(run_id) - del self.runs[run_id] + if observation is not None: + observation.update( + output=output, + input=kwargs.get("inputs"), + ).end() except Exception as e: langfuse_logger.exception(e) @@ -696,16 +708,14 @@ def on_tool_error( ) -> Any: try: self._log_debug_event("on_tool_error", run_id, parent_run_id, error=error) - if run_id is None or run_id not in self.runs: - raise Exception("run not found") - - self.runs[run_id].update( - status_message=str(error), - level="ERROR", - input=kwargs.get("inputs"), - ).end() + observation = self._detach_observation(run_id) - del self.runs[run_id] + if observation is not None: + observation.update( + status_message=str(error), + level="ERROR", + input=kwargs.get("inputs"), + ).end() except Exception as e: langfuse_logger.exception(e) @@ -753,14 +763,8 @@ def __on_llm_action( "prompt": registered_prompt, } - if parent_run_id is not None and parent_run_id in self.runs: - self.runs[run_id] = cast( - LangfuseGeneration, self.runs[parent_run_id] - ).start_observation(as_type="generation", **content) # type: ignore - else: - self.runs[run_id] = self.client.start_observation( - as_type="generation", **content - ) # type: ignore + generation = self.client.start_observation(as_type="generation", **content) # type: ignore + self._attach_observation(run_id, generation) self.last_trace_id = self.runs[run_id].trace_id @@ -856,17 +860,17 @@ def on_llm_end( # e.g. azure returns the model name in the response model = _parse_model(response) - langfuse_generation = cast(LangfuseGeneration, self.runs[run_id]) - langfuse_generation.update( - output=extracted_response, - usage=llm_usage, - usage_details=llm_usage, - input=kwargs.get("inputs"), - model=model, - ) - langfuse_generation.end() - del self.runs[run_id] + generation = self._detach_observation(run_id) + + if generation is not None: + generation.update( + output=extracted_response, + usage=llm_usage, + usage_details=llm_usage, + input=kwargs.get("inputs"), + model=model, + ).end() except Exception as e: langfuse_logger.exception(e) @@ -884,16 +888,15 @@ def on_llm_error( ) -> Any: try: self._log_debug_event("on_llm_error", run_id, parent_run_id, error=error) - if run_id in self.runs: - generation = self.runs[run_id] + + generation = self._detach_observation(run_id) + + if generation is not None: generation.update( status_message=str(error), level="ERROR", input=kwargs.get("inputs"), - ) - generation.end() - - del self.runs[run_id] + ).end() except Exception as e: langfuse_logger.exception(e) From 827fd1db6ad721e816eb5b51d0e576bac93d6a73 Mon Sep 17 00:00:00 2001 From: "P.B.Vignesh" Date: Tue, 26 Aug 2025 21:46:43 +0530 Subject: [PATCH 028/296] fix(langchain): add support for capturing usage details with ChatBedrock (#1267) Co-authored-by: Vignesh Pb Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/langchain/CallbackHandler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index ed8046e8e..431529ac0 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -994,6 +994,9 @@ def _parse_usage_model(usage: typing.Union[pydantic.BaseModel, dict]) -> Any: ("input_tokens", "input"), ("output_tokens", "output"), ("total_tokens", "total"), + # ChatBedrock API follows a separate format compared to ChatBedrockConverse API + ("prompt_tokens", "input"), + ("completion_tokens", "output"), # https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/get-token-count ("prompt_token_count", "input"), ("candidates_token_count", "output"), From bd5f72ff5fe7008370ac1fd2f904df2c86254c1b Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:52:23 +0200 Subject: [PATCH 029/296] feat(openai): add openrouter cost tracking (#1311) --- langfuse/openai.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/langfuse/openai.py b/langfuse/openai.py index 5f163db48..7dea644d9 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -494,6 +494,7 @@ def _create_langfuse_update( if usage is not None: update["usage_details"] = _parse_usage(usage) + update["cost_details"] = _parse_cost(usage) generation.update(**update) @@ -523,6 +524,18 @@ def _parse_usage(usage: Optional[Any] = None) -> Any: return usage_dict +def _parse_cost(usage: Optional[Any] = None) -> Any: + if usage is None: + return + + # OpenRouter is returning total cost of the invocation + # https://openrouter.ai/docs/use-cases/usage-accounting#cost-breakdown + if hasattr(usage, "cost") and isinstance(getattr(usage, "cost"), float): + return {"total": getattr(usage, "cost")} + + return None + + def _extract_streamed_response_api_response(chunks: Any) -> Any: completion, model, usage = None, None, None metadata = {} From 17a7bc12379898b331addf14b4d084caf60c19a1 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:18:22 +0200 Subject: [PATCH 030/296] fix(langchain): do not end span on agent callbacks (#1312) Co-authored-by: @qnnn --- langfuse/langchain/CallbackHandler.py | 69 +++++++++++---------------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 431529ac0..a1bf51bd7 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -209,10 +209,6 @@ def on_retriever_error( self._log_debug_event( "on_retriever_error", run_id, parent_run_id, error=error ) - - if run_id is None or run_id not in self.runs: - raise Exception("run not found") - observation = self._detach_observation(run_id) if observation is not None: @@ -401,19 +397,17 @@ def on_agent_action( "on_agent_action", run_id, parent_run_id, action=action ) - if run_id not in self.runs: - raise Exception("run not found") + agent_run = self.runs.get(run_id, None) - agent_run = self.runs[run_id] - if hasattr(agent_run, "_otel_span"): + if agent_run is not None: agent_run._otel_span.set_attribute( LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "agent" ) - agent_run.update( - output=action, - input=kwargs.get("inputs"), - ).end() + agent_run.update( + output=action, + input=kwargs.get("inputs"), + ) except Exception as e: langfuse_logger.exception(e) @@ -430,10 +424,9 @@ def on_agent_finish( self._log_debug_event( "on_agent_finish", run_id, parent_run_id, finish=finish ) - if run_id not in self.runs: - raise Exception("run not found") - - agent_run = self._detach_observation(run_id) + # Langchain is sending same run ID for both agent finish and chain end + # handle cleanup of observation in the chain end callback + agent_run = self.runs.get(run_id, None) if agent_run is not None: agent_run._otel_span.set_attribute( @@ -443,7 +436,7 @@ def on_agent_finish( agent_run.update( output=finish, input=kwargs.get("inputs"), - ).end() + ) except Exception as e: langfuse_logger.exception(e) @@ -461,9 +454,6 @@ def on_chain_end( "on_chain_end", run_id, parent_run_id, outputs=outputs ) - if run_id not in self.runs: - raise Exception("run not found") - span = self._detach_observation(run_id) if span is not None: @@ -846,31 +836,28 @@ def on_llm_end( self._log_debug_event( "on_llm_end", run_id, parent_run_id, response=response, kwargs=kwargs ) - if run_id not in self.runs: - raise Exception("Run not found, see docs what to do in this case.") - else: - response_generation = response.generations[-1][-1] - extracted_response = ( - self._convert_message_to_dict(response_generation.message) - if isinstance(response_generation, ChatGeneration) - else _extract_raw_response(response_generation) - ) + response_generation = response.generations[-1][-1] + extracted_response = ( + self._convert_message_to_dict(response_generation.message) + if isinstance(response_generation, ChatGeneration) + else _extract_raw_response(response_generation) + ) - llm_usage = _parse_usage(response) + llm_usage = _parse_usage(response) - # e.g. azure returns the model name in the response - model = _parse_model(response) + # e.g. azure returns the model name in the response + model = _parse_model(response) - generation = self._detach_observation(run_id) + generation = self._detach_observation(run_id) - if generation is not None: - generation.update( - output=extracted_response, - usage=llm_usage, - usage_details=llm_usage, - input=kwargs.get("inputs"), - model=model, - ).end() + if generation is not None: + generation.update( + output=extracted_response, + usage=llm_usage, + usage_details=llm_usage, + input=kwargs.get("inputs"), + model=model, + ).end() except Exception as e: langfuse_logger.exception(e) From 172a78cf53842bd251172e299f35fe3da5fff108 Mon Sep 17 00:00:00 2001 From: Jay-flow Date: Thu, 28 Aug 2025 00:36:15 +0900 Subject: [PATCH 031/296] fix: resolve GCS bad request error in media uploads. (#1265) Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/_task_manager/media_manager.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index 13fa5f0c6..a36e3b8af 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -246,15 +246,20 @@ def _process_upload_media_job( return + headers = {"Content-Type": data["content_type"]} + + # In self-hosted setups with GCP, do not add unsupported headers that fail the upload + is_self_hosted_gcs_bucket = "storage.googleapis.com" in upload_url + + if not is_self_hosted_gcs_bucket: + headers["x-ms-blob-type"] = "BlockBlob" + headers["x-amz-checksum-sha256"] = data["content_sha256_hash"] + upload_start_time = time.time() upload_response = self._request_with_backoff( requests.put, upload_url, - headers={ - "Content-Type": data["content_type"], - "x-amz-checksum-sha256": data["content_sha256_hash"], - "x-ms-blob-type": "BlockBlob", - }, + headers=headers, data=data["content_bytes"], ) upload_time_ms = int((time.time() - upload_start_time) * 1000) From a43873e46bb59cab9bd8fbf215853ef0d0dbb6b2 Mon Sep 17 00:00:00 2001 From: Gael Grosch <3279847+Digma@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:44:19 +0200 Subject: [PATCH 032/296] fix(span): fallback to client environment if not provided (#1304) --- langfuse/_client/span.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 003c58706..68c1e8c63 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -123,7 +123,7 @@ def __init__( self.trace_id = self._langfuse_client._get_otel_trace_id(otel_span) self.id = self._langfuse_client._get_otel_span_id(otel_span) - self._environment = environment + self._environment = environment or self._langfuse_client._environment if self._environment is not None: self._otel_span.set_attribute( LangfuseOtelSpanAttributes.ENVIRONMENT, self._environment From 7f61d994efdb010066d08d39e2e669b0732340ab Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:55:03 +0200 Subject: [PATCH 033/296] chore: release v3.3.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 6bc71012d..9c33aac3b 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.3.1" +__version__ = "3.3.2" diff --git a/pyproject.toml b/pyproject.toml index 54c5ee0a2..8bbb9b40f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.3.1" +version = "3.3.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 97b958b8c1a28b898d5ef904e0bdc42376f3ce02 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:59:29 +0200 Subject: [PATCH 034/296] chore: bump pdoc --- poetry.lock | 15 ++++++--------- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index c3c1217e7..5f36ff135 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1254,23 +1254,20 @@ files = [ [[package]] name = "pdoc" -version = "14.7.0" +version = "15.0.4" description = "API Documentation for Python Projects" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pdoc-14.7.0-py3-none-any.whl", hash = "sha256:72377a907efc6b2c5b3c56b717ef34f11d93621dced3b663f3aede0b844c0ad2"}, - {file = "pdoc-14.7.0.tar.gz", hash = "sha256:2d28af9c0acc39180744ad0543e4bbc3223ecba0d1302db315ec521c51f71f93"}, + {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, + {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, ] [package.dependencies] Jinja2 = ">=2.11.0" -MarkupSafe = "*" +MarkupSafe = ">=1.1.1" pygments = ">=2.12.0" -[package.extras] -dev = ["hypothesis", "mypy", "pdoc-pyo3-sample-library (==1.0.11)", "pygments (>=2.14.0)", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"] - [[package]] name = "platformdirs" version = "4.3.8" @@ -2342,4 +2339,4 @@ openai = ["openai"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "d0356916b5c7adba0ed8ea357edb8adcde09269bd83613df67976cd93a246aa3" +content-hash = "f60ed1a1461b3cadc5121d48664c0cee354169f5b12a42b28da54f74216f6f5f" diff --git a/pyproject.toml b/pyproject.toml index 8bbb9b40f..dd957f763 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ langchain-openai = ">=0.0.5,<0.3" langgraph = "^0.2.62" [tool.poetry.group.docs.dependencies] -pdoc = "^14.4.0" +pdoc = "^15.0.4" [tool.poetry.extras] openai = ["openai"] From 6303134951118118e00b85cb04e6c4ecbfba68b1 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:01:18 +0200 Subject: [PATCH 035/296] chore: bump pytest-asyncio --- poetry.lock | 561 +++++++++++++++++++++---------------------------- pyproject.toml | 2 +- 2 files changed, 240 insertions(+), 323 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5f36ff135..416b21f5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -53,6 +53,17 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + [[package]] name = "certifi" version = "2025.8.3" @@ -64,85 +75,6 @@ files = [ {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - [[package]] name = "cfgv" version = "3.4.0" @@ -308,20 +240,15 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.18.0" +version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] - [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -694,13 +621,13 @@ xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "0.3.74" +version = "0.3.75" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" files = [ - {file = "langchain_core-0.3.74-py3-none-any.whl", hash = "sha256:088338b5bc2f6a66892f9afc777992c24ee3188f41cbc603d09181e34a228ce7"}, - {file = "langchain_core-0.3.74.tar.gz", hash = "sha256:ff604441aeade942fbcc0a3860a592daba7671345230c2078ba2eb5f82b6ba76"}, + {file = "langchain_core-0.3.75-py3-none-any.whl", hash = "sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5"}, + {file = "langchain_core-0.3.75.tar.gz", hash = "sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed"}, ] [package.dependencies] @@ -790,13 +717,13 @@ orjson = ">=3.10.1" [[package]] name = "langsmith" -version = "0.4.14" +version = "0.4.19" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" files = [ - {file = "langsmith-0.4.14-py3-none-any.whl", hash = "sha256:b6d070ac425196947d2a98126fb0e35f3b8c001a2e6e5b7049dd1c56f0767d0b"}, - {file = "langsmith-0.4.14.tar.gz", hash = "sha256:4d29c7a9c85b20ba813ab9c855407bccdf5eb4f397f512ffa89959b2a2cb83ed"}, + {file = "langsmith-0.4.19-py3-none-any.whl", hash = "sha256:4c50ae47e9f8430a06adb54bceaf32808f5e54fcb8186731bf7b2dab3fc30621"}, + {file = "langsmith-0.4.19.tar.gz", hash = "sha256:71916bef574f72c40887ce371a4502d80c80efc2a053df123f1347e79ea83dca"}, ] [package.dependencies] @@ -969,13 +896,13 @@ files = [ [[package]] name = "openai" -version = "1.99.9" +version = "1.102.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" files = [ - {file = "openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a"}, - {file = "openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92"}, + {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, + {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, ] [package.dependencies] @@ -1090,94 +1017,94 @@ typing-extensions = ">=4.5.0" [[package]] name = "orjson" -version = "3.11.2" -description = "" +version = "3.11.3" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" files = [ - {file = "orjson-3.11.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6b8a78c33496230a60dc9487118c284c15ebdf6724386057239641e1eb69761"}, - {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc04036eeae11ad4180d1f7b5faddb5dab1dee49ecd147cd431523869514873b"}, - {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c04325839c5754c253ff301cee8aaed7442d974860a44447bb3be785c411c27"}, - {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32769e04cd7fdc4a59854376211145a1bbbc0aea5e9d6c9755d3d3c301d7c0df"}, - {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff285d14917ea1408a821786e3677c5261fa6095277410409c694b8e7720ae0"}, - {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2662f908114864b63ff75ffe6ffacf996418dd6cc25e02a72ad4bda81b1ec45a"}, - {file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab463cf5d08ad6623a4dac1badd20e88a5eb4b840050c4812c782e3149fe2334"}, - {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:64414241bde943cbf3c00d45fcb5223dca6d9210148ba984aae6b5d63294502b"}, - {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7773e71c0ae8c9660192ff144a3d69df89725325e3d0b6a6bb2c50e5ebaf9b84"}, - {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:652ca14e283b13ece35bf3a86503c25592f294dbcfc5bb91b20a9c9a62a3d4be"}, - {file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:26e99e98df8990ecfe3772bbdd7361f602149715c2cbc82e61af89bfad9528a4"}, - {file = "orjson-3.11.2-cp310-cp310-win32.whl", hash = "sha256:5814313b3e75a2be7fe6c7958201c16c4560e21a813dbad25920752cecd6ad66"}, - {file = "orjson-3.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc471ce2225ab4c42ca672f70600d46a8b8e28e8d4e536088c1ccdb1d22b35ce"}, - {file = "orjson-3.11.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:888b64ef7eaeeff63f773881929434a5834a6a140a63ad45183d59287f07fc6a"}, - {file = "orjson-3.11.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:83387cc8b26c9fa0ae34d1ea8861a7ae6cff8fb3e346ab53e987d085315a728e"}, - {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e35f003692c216d7ee901b6b916b5734d6fc4180fcaa44c52081f974c08e17"}, - {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a0a4c29ae90b11d0c00bcc31533854d89f77bde2649ec602f512a7e16e00640"}, - {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:585d712b1880f68370108bc5534a257b561672d1592fae54938738fe7f6f1e33"}, - {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d08e342a7143f8a7c11f1c4033efe81acbd3c98c68ba1b26b96080396019701f"}, - {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c0f84fc50398773a702732c87cd622737bf11c0721e6db3041ac7802a686fb"}, - {file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:140f84e3c8d4c142575898c91e3981000afebf0333df753a90b3435d349a5fe5"}, - {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96304a2b7235e0f3f2d9363ddccdbfb027d27338722fe469fe656832a017602e"}, - {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d7612bb227d5d9582f1f50a60bd55c64618fc22c4a32825d233a4f2771a428a"}, - {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a134587d18fe493befc2defffef2a8d27cfcada5696cb7234de54a21903ae89a"}, - {file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b84455e60c4bc12c1e4cbaa5cfc1acdc7775a9da9cec040e17232f4b05458bd"}, - {file = "orjson-3.11.2-cp311-cp311-win32.whl", hash = "sha256:f0660efeac223f0731a70884e6914a5f04d613b5ae500744c43f7bf7b78f00f9"}, - {file = "orjson-3.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:955811c8405251d9e09cbe8606ad8fdef49a451bcf5520095a5ed38c669223d8"}, - {file = "orjson-3.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:2e4d423a6f838552e3a6d9ec734b729f61f88b1124fd697eab82805ea1a2a97d"}, - {file = "orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486"}, - {file = "orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1"}, - {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131"}, - {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c"}, - {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14"}, - {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448"}, - {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c"}, - {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804"}, - {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307"}, - {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219"}, - {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45"}, - {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e"}, - {file = "orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e"}, - {file = "orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732"}, - {file = "orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36"}, - {file = "orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219"}, - {file = "orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad"}, - {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2"}, - {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe"}, - {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae"}, - {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6"}, - {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1"}, - {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa"}, - {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e"}, - {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15"}, - {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac"}, - {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8"}, - {file = "orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5"}, - {file = "orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d"}, - {file = "orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535"}, - {file = "orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7"}, - {file = "orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81"}, - {file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f"}, - {file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7"}, - {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4"}, - {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f"}, - {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7"}, - {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6"}, - {file = "orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f"}, - {file = "orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8"}, - {file = "orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67"}, - {file = "orjson-3.11.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:957f10c7b5bce3d3f2ad577f3b307c784f5dabafcce3b836229c269c11841c86"}, - {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a669e31ab8eb466c9142ac7a4be2bb2758ad236a31ef40dcd4cf8774ab40f33"}, - {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adedf7d887416c51ad49de3c53b111887e0b63db36c6eb9f846a8430952303d8"}, - {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad8873979659ad98fc56377b9c5b93eb8059bf01e6412f7abf7dbb3d637a991"}, - {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9482ef83b2bf796157566dd2d2742a8a1e377045fe6065fa67acb1cb1d21d9a3"}, - {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73cee7867c1fcbd1cc5b6688b3e13db067f968889242955780123a68b3d03316"}, - {file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:465166773265f3cc25db10199f5d11c81898a309e26a2481acf33ddbec433fda"}, - {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc000190a7b1d2d8e36cba990b3209a1e15c0efb6c7750e87f8bead01afc0d46"}, - {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:df3fdd8efa842ccbb81135d6f58a73512f11dba02ed08d9466261c2e9417af4e"}, - {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3dacfc621be3079ec69e0d4cb32e3764067726e0ef5a5576428f68b6dc85b4f6"}, - {file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9fdff73a029cde5f4a1cf5ec9dbc6acab98c9ddd69f5580c2b3f02ce43ba9f9f"}, - {file = "orjson-3.11.2-cp39-cp39-win32.whl", hash = "sha256:b1efbdc479c6451138c3733e415b4d0e16526644e54e2f3689f699c4cda303bf"}, - {file = "orjson-3.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:c9ec0cc0d4308cad1e38a1ee23b64567e2ff364c2a3fe3d6cbc69cf911c45712"}, - {file = "orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309"}, + {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, + {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, + {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467"}, + {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873"}, + {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a"}, + {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b"}, + {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf"}, + {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4"}, + {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc"}, + {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569"}, + {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6"}, + {file = "orjson-3.11.3-cp310-cp310-win32.whl", hash = "sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc"}, + {file = "orjson-3.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770"}, + {file = "orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f"}, + {file = "orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91"}, + {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904"}, + {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6"}, + {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d"}, + {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038"}, + {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb"}, + {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2"}, + {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55"}, + {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1"}, + {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824"}, + {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f"}, + {file = "orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204"}, + {file = "orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b"}, + {file = "orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e"}, + {file = "orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b"}, + {file = "orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2"}, + {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a"}, + {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c"}, + {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064"}, + {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424"}, + {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23"}, + {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667"}, + {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f"}, + {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1"}, + {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc"}, + {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049"}, + {file = "orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca"}, + {file = "orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1"}, + {file = "orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710"}, + {file = "orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810"}, + {file = "orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43"}, + {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27"}, + {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f"}, + {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c"}, + {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be"}, + {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d"}, + {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2"}, + {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f"}, + {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee"}, + {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e"}, + {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633"}, + {file = "orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b"}, + {file = "orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae"}, + {file = "orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce"}, + {file = "orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4"}, + {file = "orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e"}, + {file = "orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d"}, + {file = "orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229"}, + {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451"}, + {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167"}, + {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077"}, + {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872"}, + {file = "orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d"}, + {file = "orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804"}, + {file = "orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc"}, + {file = "orjson-3.11.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:56afaf1e9b02302ba636151cfc49929c1bb66b98794291afd0e5f20fecaf757c"}, + {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913f629adef31d2d350d41c051ce7e33cf0fd06a5d1cb28d49b1899b23b903aa"}, + {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0a23b41f8f98b4e61150a03f83e4f0d566880fe53519d445a962929a4d21045"}, + {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d721fee37380a44f9d9ce6c701b3960239f4fb3d5ceea7f31cbd43882edaa2f"}, + {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73b92a5b69f31b1a58c0c7e31080aeaec49c6e01b9522e71ff38d08f15aa56de"}, + {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2489b241c19582b3f1430cc5d732caefc1aaf378d97e7fb95b9e56bed11725f"}, + {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5189a5dab8b0312eadaf9d58d3049b6a52c454256493a557405e77a3d67ab7f"}, + {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9d8787bdfbb65a85ea76d0e96a3b1bed7bf0fbcb16d40408dc1172ad784a49d2"}, + {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:8e531abd745f51f8035e207e75e049553a86823d189a51809c078412cefb399a"}, + {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8ab962931015f170b97a3dd7bd933399c1bae8ed8ad0fb2a7151a5654b6941c7"}, + {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:124d5ba71fee9c9902c4a7baa9425e663f7f0aecf73d31d54fe3dd357d62c1a7"}, + {file = "orjson-3.11.3-cp39-cp39-win32.whl", hash = "sha256:22724d80ee5a815a44fc76274bb7ba2e7464f5564aacb6ecddaa9970a83e3225"}, + {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, + {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, ] [[package]] @@ -1270,13 +1197,13 @@ pygments = ">=2.12.0" [[package]] name = "platformdirs" -version = "4.3.8" +version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, ] [package.extras] @@ -1319,31 +1246,20 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "6.31.1" +version = "6.32.0" description = "" optional = false python-versions = ">=3.9" files = [ - {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, - {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, - {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, - {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, - {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, - {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, - {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, - {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, - {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, -] - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, + {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, + {file = "protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0"}, + {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1"}, + {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c"}, + {file = "protobuf-6.32.0-cp39-cp39-win32.whl", hash = "sha256:7db8ed09024f115ac877a1427557b838705359f047b2ff2f2b2364892d19dacb"}, + {file = "protobuf-6.32.0-cp39-cp39-win_amd64.whl", hash = "sha256:15eba1b86f193a407607112ceb9ea0ba9569aed24f93333fe9a497cf2fda37d3"}, + {file = "protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783"}, + {file = "protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2"}, ] [[package]] @@ -1517,20 +1433,22 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-asyncio" -version = "0.23.8" +version = "1.1.0" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, - {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, + {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, + {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, ] [package.dependencies] -pytest = ">=7.0.0,<9" +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<9" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.10\""} [package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] @@ -1741,13 +1659,13 @@ files = [ [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] @@ -2033,13 +1951,13 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, - {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] @@ -2222,115 +2140,114 @@ type = ["pytest-mypy"] [[package]] name = "zstandard" -version = "0.23.0" +version = "0.24.0" description = "Zstandard bindings for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, - {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, - {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, - {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, - {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, - {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, - {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, - {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, - {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, - {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, - {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, - {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, - {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, - {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, - {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, - {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, - {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"}, - {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"}, - {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"}, - {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"}, - {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"}, - {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"}, - {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"}, - {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, - {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, + {file = "zstandard-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af1394c2c5febc44e0bbf0fc6428263fa928b50d1b1982ce1d870dc793a8e5f4"}, + {file = "zstandard-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e941654cef13a1d53634ec30933722eda11f44f99e1d0bc62bbce3387580d50"}, + {file = "zstandard-0.24.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:561123d05681197c0e24eb8ab3cfdaf299e2b59c293d19dad96e1610ccd8fbc6"}, + {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0f6d9a146e07458cb41423ca2d783aefe3a3a97fe72838973c13b8f1ecc7343a"}, + {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf02f915fa7934ea5dfc8d96757729c99a8868b7c340b97704795d6413cf5fe6"}, + {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:35f13501a8accf834457d8e40e744568287a215818778bc4d79337af2f3f0d97"}, + {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92be52ca4e6e604f03d5daa079caec9e04ab4cbf6972b995aaebb877d3d24e13"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c9c3cba57f5792532a3df3f895980d47d78eda94b0e5b800651b53e96e0b604"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dd91b0134a32dfcd8be504e8e46de44ad0045a569efc25101f2a12ccd41b5759"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d6975f2d903bc354916a17b91a7aaac7299603f9ecdb788145060dde6e573a16"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7ac6e4d727521d86d20ec291a3f4e64a478e8a73eaee80af8f38ec403e77a409"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:87ae1684bc3c02d5c35884b3726525eda85307073dbefe68c3c779e104a59036"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:7de5869e616d426b56809be7dc6dba4d37b95b90411ccd3de47f421a42d4d42c"}, + {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:388aad2d693707f4a0f6cc687eb457b33303d6b57ecf212c8ff4468c34426892"}, + {file = "zstandard-0.24.0-cp310-cp310-win32.whl", hash = "sha256:962ea3aecedcc944f8034812e23d7200d52c6e32765b8da396eeb8b8ffca71ce"}, + {file = "zstandard-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:869bf13f66b124b13be37dd6e08e4b728948ff9735308694e0b0479119e08ea7"}, + {file = "zstandard-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:addfc23e3bd5f4b6787b9ca95b2d09a1a67ad5a3c318daaa783ff90b2d3a366e"}, + {file = "zstandard-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b005bcee4be9c3984b355336283afe77b2defa76ed6b89332eced7b6fa68b68"}, + {file = "zstandard-0.24.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:3f96a9130171e01dbb6c3d4d9925d604e2131a97f540e223b88ba45daf56d6fb"}, + {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd0d3d16e63873253bad22b413ec679cf6586e51b5772eb10733899832efec42"}, + {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b7a8c30d9bf4bd5e4dcfe26900bef0fcd9749acde45cdf0b3c89e2052fda9a13"}, + {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:52cd7d9fa0a115c9446abb79b06a47171b7d916c35c10e0c3aa6f01d57561382"}, + {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0f6fc2ea6e07e20df48752e7700e02e1892c61f9a6bfbacaf2c5b24d5ad504b"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e46eb6702691b24ddb3e31e88b4a499e31506991db3d3724a85bd1c5fc3cfe4e"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5e3b9310fd7f0d12edc75532cd9a56da6293840c84da90070d692e0bb15f186"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76cdfe7f920738ea871f035568f82bad3328cbc8d98f1f6988264096b5264efd"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f2fe35ec84908dddf0fbf66b35d7c2878dbe349552dd52e005c755d3493d61c"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:aa705beb74ab116563f4ce784fa94771f230c05d09ab5de9c397793e725bb1db"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:aadf32c389bb7f02b8ec5c243c38302b92c006da565e120dfcb7bf0378f4f848"}, + {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e40cd0fc734aa1d4bd0e7ad102fd2a1aefa50ce9ef570005ffc2273c5442ddc3"}, + {file = "zstandard-0.24.0-cp311-cp311-win32.whl", hash = "sha256:cda61c46343809ecda43dc620d1333dd7433a25d0a252f2dcc7667f6331c7b61"}, + {file = "zstandard-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b95fc06489aa9388400d1aab01a83652bc040c9c087bd732eb214909d7fb0dd"}, + {file = "zstandard-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad9fd176ff6800a0cf52bcf59c71e5de4fa25bf3ba62b58800e0f84885344d34"}, + {file = "zstandard-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2bda8f2790add22773ee7a4e43c90ea05598bffc94c21c40ae0a9000b0133c3"}, + {file = "zstandard-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc76de75300f65b8eb574d855c12518dc25a075dadb41dd18f6322bda3fe15d5"}, + {file = "zstandard-0.24.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d2b3b4bda1a025b10fe0269369475f420177f2cb06e0f9d32c95b4873c9f80b8"}, + {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b84c6c210684286e504022d11ec294d2b7922d66c823e87575d8b23eba7c81f"}, + {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c59740682a686bf835a1a4d8d0ed1eefe31ac07f1c5a7ed5f2e72cf577692b00"}, + {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6324fde5cf5120fbf6541d5ff3c86011ec056e8d0f915d8e7822926a5377193a"}, + {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51a86bd963de3f36688553926a84e550d45d7f9745bd1947d79472eca27fcc75"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d82ac87017b734f2fb70ff93818c66f0ad2c3810f61040f077ed38d924e19980"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92ea7855d5bcfb386c34557516c73753435fb2d4a014e2c9343b5f5ba148b5d8"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3adb4b5414febf074800d264ddf69ecade8c658837a83a19e8ab820e924c9933"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6374feaf347e6b83ec13cc5dcfa70076f06d8f7ecd46cc71d58fac798ff08b76"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:13fc548e214df08d896ee5f29e1f91ee35db14f733fef8eabea8dca6e451d1e2"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0a416814608610abf5488889c74e43ffa0343ca6cf43957c6b6ec526212422da"}, + {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0d66da2649bb0af4471699aeb7a83d6f59ae30236fb9f6b5d20fb618ef6c6777"}, + {file = "zstandard-0.24.0-cp312-cp312-win32.whl", hash = "sha256:ff19efaa33e7f136fe95f9bbcc90ab7fb60648453b03f95d1de3ab6997de0f32"}, + {file = "zstandard-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc05f8a875eb651d1cc62e12a4a0e6afa5cd0cc231381adb830d2e9c196ea895"}, + {file = "zstandard-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:b04c94718f7a8ed7cdd01b162b6caa1954b3c9d486f00ecbbd300f149d2b2606"}, + {file = "zstandard-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e4ebb000c0fe24a6d0f3534b6256844d9dbf042fdf003efe5cf40690cf4e0f3e"}, + {file = "zstandard-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:498f88f5109666c19531f0243a90d2fdd2252839cd6c8cc6e9213a3446670fa8"}, + {file = "zstandard-0.24.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0a9e95ceb180ccd12a8b3437bac7e8a8a089c9094e39522900a8917745542184"}, + {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bcf69e0bcddbf2adcfafc1a7e864edcc204dd8171756d3a8f3340f6f6cc87b7b"}, + {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:10e284748a7e7fbe2815ca62a9d6e84497d34cfdd0143fa9e8e208efa808d7c4"}, + {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1bda8a85e5b9d5e73af2e61b23609a8cc1598c1b3b2473969912979205a1ff25"}, + {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b14bc92af065d0534856bf1b30fc48753163ea673da98857ea4932be62079b1"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:b4f20417a4f511c656762b001ec827500cbee54d1810253c6ca2df2c0a307a5f"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:337572a7340e1d92fd7fb5248c8300d0e91071002d92e0b8cabe8d9ae7b58159"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df4be1cf6e8f0f2bbe2a3eabfff163ef592c84a40e1a20a8d7db7f27cfe08fc2"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6885ae4b33aee8835dbdb4249d3dfec09af55e705d74d9b660bfb9da51baaa8b"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:663848a8bac4fdbba27feea2926049fdf7b55ec545d5b9aea096ef21e7f0b079"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:05d27c953f2e0a3ecc8edbe91d6827736acc4c04d0479672e0400ccdb23d818c"}, + {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77b8b7b98893eaf47da03d262816f01f251c2aa059c063ed8a45c50eada123a5"}, + {file = "zstandard-0.24.0-cp313-cp313-win32.whl", hash = "sha256:cf7fbb4e54136e9a03c7ed7691843c4df6d2ecc854a2541f840665f4f2bb2edd"}, + {file = "zstandard-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:d64899cc0f33a8f446f1e60bffc21fa88b99f0e8208750d9144ea717610a80ce"}, + {file = "zstandard-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:57be3abb4313e0dd625596376bbb607f40059d801d51c1a1da94d7477e63b255"}, + {file = "zstandard-0.24.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b7fa260dd2731afd0dfa47881c30239f422d00faee4b8b341d3e597cface1483"}, + {file = "zstandard-0.24.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e05d66239d14a04b4717998b736a25494372b1b2409339b04bf42aa4663bf251"}, + {file = "zstandard-0.24.0-cp314-cp314-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:622e1e04bd8a085994e02313ba06fbcf4f9ed9a488c6a77a8dbc0692abab6a38"}, + {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:55872e818598319f065e8192ebefecd6ac05f62a43f055ed71884b0a26218f41"}, + {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bb2446a55b3a0fd8aa02aa7194bd64740015464a2daaf160d2025204e1d7c282"}, + {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2825a3951f945fb2613ded0f517d402b1e5a68e87e0ee65f5bd224a8333a9a46"}, + {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09887301001e7a81a3618156bc1759e48588de24bddfdd5b7a4364da9a8fbc20"}, + {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:98ca91dc9602cf351497d5600aa66e6d011a38c085a8237b370433fcb53e3409"}, + {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e69f8e534b4e254f523e2f9d4732cf9c169c327ca1ce0922682aac9a5ee01155"}, + {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:444633b487a711e34f4bccc46a0c5dfbe1aee82c1a511e58cdc16f6bd66f187c"}, + {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f7d3fe9e1483171e9183ffdb1fab07c5fef80a9c3840374a38ec2ab869ebae20"}, + {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:27b6fa72b57824a3f7901fc9cc4ce1c1c834b28f3a43d1d4254c64c8f11149d4"}, + {file = "zstandard-0.24.0-cp314-cp314-win32.whl", hash = "sha256:fdc7a52a4cdaf7293e10813fd6a3abc0c7753660db12a3b864ab1fb5a0c60c16"}, + {file = "zstandard-0.24.0-cp314-cp314-win_amd64.whl", hash = "sha256:656ed895b28c7e42dd5b40dfcea3217cfc166b6b7eef88c3da2f5fc62484035b"}, + {file = "zstandard-0.24.0-cp314-cp314-win_arm64.whl", hash = "sha256:0101f835da7de08375f380192ff75135527e46e3f79bef224e3c49cb640fef6a"}, + {file = "zstandard-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52788e7c489069e317fde641de41b757fa0ddc150e06488f153dd5daebac7192"}, + {file = "zstandard-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec194197e90ca063f5ecb935d6c10063d84208cac5423c07d0f1a09d1c2ea42b"}, + {file = "zstandard-0.24.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e91a4e5d62da7cb3f53e04fe254f1aa41009af578801ee6477fe56e7bef74ee2"}, + {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fc67eb15ed573950bc6436a04b3faea6c36c7db98d2db030d48391c6736a0dc"}, + {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f6ae9fc67e636fc0fa9adee39db87dfbdeabfa8420bc0e678a1ac8441e01b22b"}, + {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ab2357353894a5ec084bb8508ff892aa43fb7fe8a69ad310eac58221ee7f72aa"}, + {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f578fab202f4df67a955145c3e3ca60ccaaaf66c97808545b2625efeecdef10"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c39d2b6161f3c5c5d12e9207ecf1006bb661a647a97a6573656b09aaea3f00ef"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dc5654586613aebe5405c1ba180e67b3f29e7d98cf3187c79efdcc172f39457"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b91380aefa9c7ac831b011368daf378d3277e0bdeb6bad9535e21251e26dd55a"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:010302face38c9a909b8934e3bf6038266d6afc69523f3efa023c5cb5d38271b"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:3aa3b4344b206941385a425ea25e6dd63e5cb0f535a4b88d56e3f8902086be9e"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:63d39b161000aeeaa06a1cb77c9806e939bfe460dfd593e4cbf24e6bc717ae94"}, + {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed8345b504df1cab280af923ef69ec0d7d52f7b22f78ec7982fde7c33a43c4f"}, + {file = "zstandard-0.24.0-cp39-cp39-win32.whl", hash = "sha256:1e133a9dd51ac0bcd5fd547ba7da45a58346dbc63def883f999857b0d0c003c4"}, + {file = "zstandard-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:8ecd3b1f7a601f79e0cd20c26057d770219c0dc2f572ea07390248da2def79a4"}, + {file = "zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f"}, ] -[package.dependencies] -cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} - [package.extras] -cffi = ["cffi (>=1.11)"] +cffi = ["cffi (>=1.17)"] [extras] langchain = ["langchain"] @@ -2339,4 +2256,4 @@ openai = ["openai"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "f60ed1a1461b3cadc5121d48664c0cee354169f5b12a42b28da54f74216f6f5f" +content-hash = "cbd2c17ab4ed8bc092368e8c974a404457219241c323b135615518f3021dc409" diff --git a/pyproject.toml b/pyproject.toml index dd957f763..737c9cce0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ pytest = ">=7.4,<9.0" pytest-timeout = "^2.1.0" pytest-xdist = "^3.3.1" pre-commit = "^3.2.2" -pytest-asyncio = ">=0.21.1,<0.24.0" +pytest-asyncio = ">=0.21.1,<1.2.0" pytest-httpserver = "^1.0.8" ruff = ">=0.1.8,<0.6.0" mypy = "^1.0.0" From 35142faf3bc2a66bdee50e901d8e7fa9088786e2 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:03:24 +0200 Subject: [PATCH 036/296] chore: bump ruff, langchain_openai and langgraph --- poetry.lock | 223 +++++++++++++++++++++++++++++++++++++++++-------- pyproject.toml | 6 +- 2 files changed, 190 insertions(+), 39 deletions(-) diff --git a/poetry.lock b/poetry.lock index 416b21f5a..cf5392bfd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -641,18 +641,18 @@ typing-extensions = ">=4.7" [[package]] name = "langchain-openai" -version = "0.2.14" +version = "0.3.32" description = "An integration package connecting OpenAI and LangChain" optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" files = [ - {file = "langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8"}, - {file = "langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978"}, + {file = "langchain_openai-0.3.32-py3-none-any.whl", hash = "sha256:3354f76822f7cc76d8069831fe2a77f9bc7ff3b4f13af788bd94e4c6e853b400"}, + {file = "langchain_openai-0.3.32.tar.gz", hash = "sha256:782ad669bd1bdb964456d8882c5178717adcfceecb482cc20005f770e43d346d"}, ] [package.dependencies] -langchain-core = ">=0.3.27,<0.4.0" -openai = ">=1.58.1,<2.0.0" +langchain-core = ">=0.3.74,<1.0.0" +openai = ">=1.99.9,<2.0.0" tiktoken = ">=0.7,<1" [[package]] @@ -671,19 +671,22 @@ langchain-core = ">=0.3.72,<1.0.0" [[package]] name = "langgraph" -version = "0.2.76" +version = "0.6.6" description = "Building stateful, multi-actor applications with LLMs" optional = false -python-versions = "<4.0,>=3.9.0" +python-versions = ">=3.9" files = [ - {file = "langgraph-0.2.76-py3-none-any.whl", hash = "sha256:076b8b5d2fc5a9761c46a7618430cfa5c978a8012257c43cbc127b27e0fd7872"}, - {file = "langgraph-0.2.76.tar.gz", hash = "sha256:688f8dcd9b6797ba78384599e0de944773000c75156ad1e186490e99e89fa5c0"}, + {file = "langgraph-0.6.6-py3-none-any.whl", hash = "sha256:a2283a5236abba6c8307c1a485c04e8a0f0ffa2be770878782a7bf2deb8d7954"}, + {file = "langgraph-0.6.6.tar.gz", hash = "sha256:e7d3cefacf356f8c01721b166b67b3bf581659d5361a3530f59ecd9b8448eca7"}, ] [package.dependencies] -langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.3.15 || >0.3.15,<0.3.16 || >0.3.16,<0.3.17 || >0.3.17,<0.3.18 || >0.3.18,<0.3.19 || >0.3.19,<0.3.20 || >0.3.20,<0.3.21 || >0.3.21,<0.3.22 || >0.3.22,<0.4.0" -langgraph-checkpoint = ">=2.0.10,<3.0.0" -langgraph-sdk = ">=0.1.42,<0.2.0" +langchain-core = ">=0.1" +langgraph-checkpoint = ">=2.1.0,<3.0.0" +langgraph-prebuilt = ">=0.6.0,<0.7.0" +langgraph-sdk = ">=0.2.2,<0.3.0" +pydantic = ">=2.7.4" +xxhash = ">=3.5.0" [[package]] name = "langgraph-checkpoint" @@ -700,15 +703,30 @@ files = [ langchain-core = ">=0.2.38" ormsgpack = ">=1.10.0" +[[package]] +name = "langgraph-prebuilt" +version = "0.6.4" +description = "Library with high-level APIs for creating and executing LangGraph agents and tools." +optional = false +python-versions = ">=3.9" +files = [ + {file = "langgraph_prebuilt-0.6.4-py3-none-any.whl", hash = "sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344"}, + {file = "langgraph_prebuilt-0.6.4.tar.gz", hash = "sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f"}, +] + +[package.dependencies] +langchain-core = ">=0.3.67" +langgraph-checkpoint = ">=2.1.0,<3.0.0" + [[package]] name = "langgraph-sdk" -version = "0.1.74" +version = "0.2.3" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" files = [ - {file = "langgraph_sdk-0.1.74-py3-none-any.whl", hash = "sha256:3a265c3757fe0048adad4391d10486db63ef7aa5a2cbd22da22d4503554cb890"}, - {file = "langgraph_sdk-0.1.74.tar.gz", hash = "sha256:7450e0db5b226cc2e5328ca22c5968725873630ef47c4206a30707cb25dc3ad6"}, + {file = "langgraph_sdk-0.2.3-py3-none-any.whl", hash = "sha256:059edfe2f62708c2e54239e170f5a33f796d456dbdbde64276c16cac8b97ba99"}, + {file = "langgraph_sdk-0.2.3.tar.gz", hash = "sha256:17398aeae0f937cae1c8eb9027ada2969abdb50fe8ed3246c78f543b679cf959"}, ] [package.dependencies] @@ -1694,29 +1712,30 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "ruff" -version = "0.5.7" +version = "0.12.10" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, - {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, - {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"}, - {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"}, - {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"}, - {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"}, - {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, + {file = "ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b"}, + {file = "ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1"}, + {file = "ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9"}, + {file = "ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a"}, + {file = "ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60"}, + {file = "ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56"}, + {file = "ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9"}, + {file = "ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b"}, + {file = "ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266"}, + {file = "ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e"}, + {file = "ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc"}, + {file = "ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9"}, ] [[package]] @@ -2119,6 +2138,138 @@ files = [ {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, ] +[[package]] +name = "xxhash" +version = "3.5.0" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +files = [ + {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, + {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442"}, + {file = "xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da"}, + {file = "xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9"}, + {file = "xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839"}, + {file = "xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da"}, + {file = "xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58"}, + {file = "xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"}, + {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"}, + {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"}, + {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"}, + {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"}, + {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"}, + {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"}, + {file = "xxhash-3.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e5f70f6dca1d3b09bccb7daf4e087075ff776e3da9ac870f86ca316736bb4aa"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e76e83efc7b443052dd1e585a76201e40b3411fe3da7af4fe434ec51b2f163b"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33eac61d0796ca0591f94548dcfe37bb193671e0c9bcf065789b5792f2eda644"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ec70a89be933ea49222fafc3999987d7899fc676f688dd12252509434636622"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86b8e7f703ec6ff4f351cfdb9f428955859537125904aa8c963604f2e9d3e7"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0adfbd36003d9f86c8c97110039f7539b379f28656a04097e7434d3eaf9aa131"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:63107013578c8a730419adc05608756c3fa640bdc6abe806c3123a49fb829f43"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:683b94dbd1ca67557850b86423318a2e323511648f9f3f7b1840408a02b9a48c"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5d2a01dcce81789cf4b12d478b5464632204f4c834dc2d064902ee27d2d1f0ee"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a9d360a792cbcce2fe7b66b8d51274ec297c53cbc423401480e53b26161a290d"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f0b48edbebea1b7421a9c687c304f7b44d0677c46498a046079d445454504737"}, + {file = "xxhash-3.5.0-cp37-cp37m-win32.whl", hash = "sha256:7ccb800c9418e438b44b060a32adeb8393764da7441eb52aa2aa195448935306"}, + {file = "xxhash-3.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c3bc7bf8cb8806f8d1c9bf149c18708cb1c406520097d6b0a73977460ea03602"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74752ecaa544657d88b1d1c94ae68031e364a4d47005a90288f3bab3da3c970f"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dee1316133c9b463aa81aca676bc506d3f80d8f65aeb0bba2b78d0b30c51d7bd"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:602d339548d35a8579c6b013339fb34aee2df9b4e105f985443d2860e4d7ffaa"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:695735deeddfb35da1677dbc16a083445360e37ff46d8ac5c6fcd64917ff9ade"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1030a39ba01b0c519b1a82f80e8802630d16ab95dc3f2b2386a0b5c8ed5cbb10"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bc08f33c4966f4eb6590d6ff3ceae76151ad744576b5fc6c4ba8edd459fdec"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e0c19ee500482ddfb5d5570a0415f565d8ae2b3fd69c5dcfce8a58107b1c3"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f1abffa122452481a61c3551ab3c89d72238e279e517705b8b03847b1d93d738"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d5e9db7ef3ecbfc0b4733579cea45713a76852b002cf605420b12ef3ef1ec148"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:23241ff6423378a731d84864bf923a41649dc67b144debd1077f02e6249a0d54"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:82b833d5563fefd6fceafb1aed2f3f3ebe19f84760fdd289f8b926731c2e6e91"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a80ad0ffd78bef9509eee27b4a29e56f5414b87fb01a888353e3d5bda7038bd"}, + {file = "xxhash-3.5.0-cp38-cp38-win32.whl", hash = "sha256:50ac2184ffb1b999e11e27c7e3e70cc1139047e7ebc1aa95ed12f4269abe98d4"}, + {file = "xxhash-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:392f52ebbb932db566973693de48f15ce787cabd15cf6334e855ed22ea0be5b3"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606"}, + {file = "xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4"}, + {file = "xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558"}, + {file = "xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b4154c00eb22e4d543f472cfca430e7962a0f1d0f3778334f2e08a7ba59363c"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d30bbc1644f726b825b3278764240f449d75f1a8bdda892e641d4a688b1494ae"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa0b72f2423e2aa53077e54a61c28e181d23effeaafd73fcb9c494e60930c8e"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13de2b76c1835399b2e419a296d5b38dc4855385d9e96916299170085ef72f57"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0691bfcc4f9c656bcb96cc5db94b4d75980b9d5589f2e59de790091028580837"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:297595fe6138d4da2c8ce9e72a04d73e58725bb60f3a19048bc96ab2ff31c692"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1276d369452040cbb943300dc8abeedab14245ea44056a2943183822513a18"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2061188a1ba352fc699c82bff722f4baacb4b4b8b2f0c745d2001e56d0dfb514"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c384c434021e4f62b8d9ba0bc9467e14d394893077e2c66d826243025e1f81"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e6a4dd644d72ab316b580a1c120b375890e4c52ec392d4aef3c63361ec4d77d1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240"}, + {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, +] + [[package]] name = "zipp" version = "3.23.0" @@ -2256,4 +2407,4 @@ openai = ["openai"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "cbd2c17ab4ed8bc092368e8c974a404457219241c323b135615518f3021dc409" +content-hash = "6fe7fed47d629061be2cfcd2a2ea4c83201e5de130faf5f664d68845c2fea22f" diff --git a/pyproject.toml b/pyproject.toml index 737c9cce0..16a58d834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,10 +28,10 @@ pytest-xdist = "^3.3.1" pre-commit = "^3.2.2" pytest-asyncio = ">=0.21.1,<1.2.0" pytest-httpserver = "^1.0.8" -ruff = ">=0.1.8,<0.6.0" +ruff = ">=0.1.8,<0.13.0" mypy = "^1.0.0" -langchain-openai = ">=0.0.5,<0.3" -langgraph = "^0.2.62" +langchain-openai = ">=0.0.5,<0.4" +langgraph = ">=0.2.62,<0.7.0" [tool.poetry.group.docs.dependencies] pdoc = "^15.0.4" From 355fa18b39e7be86d5eecd6396b384b44d6f7f8c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:35:57 +0200 Subject: [PATCH 037/296] fix(langchain): fix hidden count test no longer relevant with langgraph 0.6 --- tests/test_langchain.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 0a3ac72f1..deac5de7d 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -28,7 +28,6 @@ from langfuse._client.client import Langfuse from langfuse.langchain import CallbackHandler -from langfuse.langchain.CallbackHandler import LANGSMITH_TAG_HIDDEN from tests.utils import create_uuid, encode_file_to_base64, get_api @@ -1291,17 +1290,7 @@ def call_model(state: MessagesState): trace = get_api().trace.get(trace_id=trace_id) - hidden_count = 0 - - for observation in trace.observations: - if LANGSMITH_TAG_HIDDEN in observation.metadata.get("tags", []): - hidden_count += 1 - assert observation.level == "DEBUG" - - else: - assert observation.level == "DEFAULT" - - assert hidden_count > 0 + assert len(trace.observations) > 0 @pytest.mark.skip(reason="Flaky test") @@ -1417,8 +1406,8 @@ def test_tool(x: str) -> str: pass # for type RETRIEVER - from langchain_core.retrievers import BaseRetriever from langchain_core.documents import Document + from langchain_core.retrievers import BaseRetriever class SimpleRetriever(BaseRetriever): def _get_relevant_documents(self, query: str, *, run_manager): From 0cb43cd992add3a6b4b63777ba2e7767cee8d055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 05:16:36 +0000 Subject: [PATCH 038/296] chore(deps-dev): bump ruff from 0.12.10 to 0.12.11 (#1315) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.10 to 0.12.11. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.10...0.12.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.11 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 168 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 137 insertions(+), 31 deletions(-) diff --git a/poetry.lock b/poetry.lock index cf5392bfd..3380643bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "4.10.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, @@ -37,6 +39,8 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"langchain\" and python_version < \"3.11\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -48,6 +52,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -59,6 +64,8 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -70,6 +77,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -81,6 +89,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -92,6 +101,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -180,10 +190,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "extra == \"openai\" and platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -191,6 +203,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -202,10 +215,12 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "exceptiongroup" @@ -213,6 +228,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -230,6 +247,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -244,6 +262,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -255,6 +274,7 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -272,6 +292,8 @@ version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"langchain\"" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -339,6 +361,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -350,6 +373,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -371,6 +395,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -383,7 +408,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -395,6 +420,7 @@ version = "2.6.13" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, @@ -409,6 +435,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -423,6 +450,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -432,12 +460,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -446,6 +474,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -457,6 +486,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -474,6 +504,7 @@ version = "0.10.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, @@ -553,6 +584,7 @@ files = [ {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "jsonpatch" @@ -560,10 +592,12 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -574,10 +608,12 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "langchain" @@ -585,6 +621,8 @@ version = "0.3.27" description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.9" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, @@ -625,10 +663,12 @@ version = "0.3.75" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.75-py3-none-any.whl", hash = "sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5"}, {file = "langchain_core-0.3.75.tar.gz", hash = "sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" @@ -645,6 +685,7 @@ version = "0.3.32" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langchain_openai-0.3.32-py3-none-any.whl", hash = "sha256:3354f76822f7cc76d8069831fe2a77f9bc7ff3b4f13af788bd94e4c6e853b400"}, {file = "langchain_openai-0.3.32.tar.gz", hash = "sha256:782ad669bd1bdb964456d8882c5178717adcfceecb482cc20005f770e43d346d"}, @@ -661,6 +702,8 @@ version = "0.3.9" description = "LangChain text splitting utilities" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, @@ -675,6 +718,7 @@ version = "0.6.6" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph-0.6.6-py3-none-any.whl", hash = "sha256:a2283a5236abba6c8307c1a485c04e8a0f0ffa2be770878782a7bf2deb8d7954"}, {file = "langgraph-0.6.6.tar.gz", hash = "sha256:e7d3cefacf356f8c01721b166b67b3bf581659d5361a3530f59ecd9b8448eca7"}, @@ -694,6 +738,7 @@ version = "2.1.1" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, @@ -709,6 +754,7 @@ version = "0.6.4" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_prebuilt-0.6.4-py3-none-any.whl", hash = "sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344"}, {file = "langgraph_prebuilt-0.6.4.tar.gz", hash = "sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f"}, @@ -724,6 +770,7 @@ version = "0.2.3" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_sdk-0.2.3-py3-none-any.whl", hash = "sha256:059edfe2f62708c2e54239e170f5a33f796d456dbdbde64276c16cac8b97ba99"}, {file = "langgraph_sdk-0.2.3.tar.gz", hash = "sha256:17398aeae0f937cae1c8eb9027ada2969abdb50fe8ed3246c78f543b679cf959"}, @@ -739,10 +786,12 @@ version = "0.4.19" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "langsmith-0.4.19-py3-none-any.whl", hash = "sha256:4c50ae47e9f8430a06adb54bceaf32808f5e54fcb8186731bf7b2dab3fc30621"}, {file = "langsmith-0.4.19.tar.gz", hash = "sha256:71916bef574f72c40887ce371a4502d80c80efc2a053df123f1347e79ea83dca"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -766,6 +815,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -836,6 +886,7 @@ version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, @@ -896,6 +947,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -907,6 +959,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -918,10 +971,12 @@ version = "1.102.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -945,6 +1000,7 @@ version = "1.36.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, @@ -960,6 +1016,7 @@ version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, @@ -974,6 +1031,7 @@ version = "1.36.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, @@ -994,6 +1052,7 @@ version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, @@ -1008,6 +1067,7 @@ version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, @@ -1024,6 +1084,7 @@ version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, @@ -1039,6 +1100,7 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1124,6 +1186,7 @@ files = [ {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, ] +markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} [[package]] name = "ormsgpack" @@ -1131,6 +1194,7 @@ version = "1.10.0" description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, @@ -1181,6 +1245,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1192,6 +1257,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1203,6 +1269,7 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1219,6 +1286,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -1235,6 +1303,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1250,6 +1319,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1268,6 +1338,7 @@ version = "6.32.0" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, @@ -1286,6 +1357,7 @@ version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, @@ -1299,7 +1371,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1307,6 +1379,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1418,6 +1491,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1432,6 +1506,7 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -1455,6 +1530,7 @@ version = "1.1.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, @@ -1475,6 +1551,7 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1489,6 +1566,7 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1503,6 +1581,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1523,6 +1602,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1578,6 +1658,7 @@ files = [ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "regex" @@ -1585,6 +1666,7 @@ version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, @@ -1681,6 +1763,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1702,40 +1785,43 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" [[package]] name = "ruff" -version = "0.12.10" +version = "0.12.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -files = [ - {file = "ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b"}, - {file = "ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1"}, - {file = "ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9"}, - {file = "ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a"}, - {file = "ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60"}, - {file = "ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56"}, - {file = "ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9"}, - {file = "ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b"}, - {file = "ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266"}, - {file = "ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e"}, - {file = "ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc"}, - {file = "ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9"}, +groups = ["dev"] +files = [ + {file = "ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065"}, + {file = "ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93"}, + {file = "ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8"}, + {file = "ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f"}, + {file = "ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000"}, + {file = "ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2"}, + {file = "ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39"}, + {file = "ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9"}, + {file = "ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3"}, + {file = "ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd"}, + {file = "ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea"}, + {file = "ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d"}, ] [[package]] @@ -1744,6 +1830,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1755,6 +1842,8 @@ version = "2.0.43" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"}, {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"}, @@ -1850,10 +1939,12 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx"] @@ -1865,6 +1956,7 @@ version = "0.11.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tiktoken-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917"}, {file = "tiktoken-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0"}, @@ -1912,6 +2004,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1953,10 +2047,12 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -1974,6 +2070,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -1985,6 +2082,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -1999,13 +2097,14 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2016,6 +2115,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -2029,7 +2129,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "werkzeug" @@ -2037,6 +2137,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2054,6 +2155,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2144,6 +2246,7 @@ version = "3.5.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, @@ -2276,13 +2379,14 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2295,6 +2399,7 @@ version = "0.24.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "zstandard-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af1394c2c5febc44e0bbf0fc6428263fa928b50d1b1982ce1d870dc793a8e5f4"}, {file = "zstandard-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e941654cef13a1d53634ec30933722eda11f44f99e1d0bc62bbce3387580d50"}, @@ -2396,15 +2501,16 @@ files = [ {file = "zstandard-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:8ecd3b1f7a601f79e0cd20c26057d770219c0dc2f572ea07390248da2def79a4"}, {file = "zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] -cffi = ["cffi (>=1.17)"] +cffi = ["cffi (>=1.17) ; python_version >= \"3.13\" and platform_python_implementation != \"PyPy\""] [extras] langchain = ["langchain"] openai = ["openai"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<4.0" content-hash = "6fe7fed47d629061be2cfcd2a2ea4c83201e5de130faf5f664d68845c2fea22f" From 29d75d59c502aac104132a4c24a67782cfa5fa45 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:02:25 +0200 Subject: [PATCH 039/296] fix(langchain): token detach exceptions in async contexts (#1317) --- langfuse/langchain/CallbackHandler.py | 42 +++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index a1bf51bd7..0c4fb5c2d 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -3,8 +3,10 @@ import pydantic from opentelemetry import context, trace +from opentelemetry.context import _RUNTIME_CONTEXT from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.client import Langfuse from langfuse._client.get_client import get_client from langfuse._client.span import ( LangfuseAgent, @@ -272,7 +274,7 @@ def on_chain_start( serialized, "chain", **kwargs ) - span = self.client.start_observation( + span = self._get_parent_observation(parent_run_id).start_observation( name=span_name, as_type=observation_type, metadata=span_metadata, @@ -336,6 +338,22 @@ def _deregister_langfuse_prompt(self, run_id: Optional[UUID]) -> None: if run_id is not None and run_id in self.prompt_to_parent_run_map: del self.prompt_to_parent_run_map[run_id] + def _get_parent_observation( + self, parent_run_id: Optional[UUID] + ) -> Union[ + Langfuse, + LangfuseAgent, + LangfuseChain, + LangfuseGeneration, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, + ]: + if parent_run_id and parent_run_id in self.runs: + return self.runs[parent_run_id] + + return self.client + def _attach_observation( self, run_id: UUID, @@ -369,7 +387,18 @@ def _detach_observation( token = self.context_tokens.pop(run_id, None) if token: - context.detach(token) + try: + # Directly detach from runtime context to avoid error logging + _RUNTIME_CONTEXT.detach(token) + except Exception: + # Context detach can fail in async scenarios - this is expected and safe to ignore + # The span itself was properly ended and tracing data is correctly captured + # + # Examples: + # 1. Token created in one async task/thread, detached in another + # 2. Context already detached by framework or other handlers + # 3. Runtime context state mismatch in concurrent execution + pass return cast( Union[ @@ -591,7 +620,7 @@ def on_tool_start( serialized, "tool", **kwargs ) - span = self.client.start_observation( + span = self._get_parent_observation(parent_run_id).start_observation( name=self.get_langchain_run_name(serialized, **kwargs), as_type=observation_type, input=input_str, @@ -626,8 +655,7 @@ def on_retriever_start( observation_type = self._get_observation_type_from_serialized( serialized, "retriever", **kwargs ) - - span = self.client.start_observation( + span = self._get_parent_observation(parent_run_id).start_observation( name=span_name, as_type=observation_type, metadata=span_metadata, @@ -753,7 +781,9 @@ def __on_llm_action( "prompt": registered_prompt, } - generation = self.client.start_observation(as_type="generation", **content) # type: ignore + generation = self._get_parent_observation(parent_run_id).start_observation( + as_type="generation", **content + ) # type: ignore self._attach_observation(run_id, generation) self.last_trace_id = self.runs[run_id].trace_id From ee4096b948c10c2a4ca1b248bd552212c39aff5d Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:02:43 +0200 Subject: [PATCH 040/296] chore: release v3.3.3 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 9c33aac3b..1e27fd168 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.3.2" +__version__ = "3.3.3" diff --git a/pyproject.toml b/pyproject.toml index 16a58d834..1dee4cac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.3.2" +version = "3.3.3" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 651a177d862422434883d672fa2294bd3d7a8514 Mon Sep 17 00:00:00 2001 From: Cem Asma Date: Tue, 2 Sep 2025 13:20:17 +0300 Subject: [PATCH 041/296] feat(prompts): clear prompt cache (#1278) --- langfuse/_client/client.py | 11 +++++++ langfuse/_utils/prompt_cache.py | 4 +++ tests/test_prompt.py | 53 ++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 18eca4bfd..5655dd7ce 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2996,3 +2996,14 @@ def _url_encode(self, url: str, *, is_url_param: Optional[bool] = False) -> str: # we need add safe="" to force escaping of slashes # This is necessary for prompts in prompt folders return urllib.parse.quote(url, safe="") + + def clear_prompt_cache(self): + """ + Clear the entire prompt cache, removing all cached prompts. + + This method is useful when you want to force a complete refresh of all + cached prompts, for example after major updates or when you need to + ensure the latest versions are fetched from the server. + """ + if self._resources is not None: + self._resources.prompt_cache.clear() diff --git a/langfuse/_utils/prompt_cache.py b/langfuse/_utils/prompt_cache.py index 132dcb410..99abb759f 100644 --- a/langfuse/_utils/prompt_cache.py +++ b/langfuse/_utils/prompt_cache.py @@ -162,6 +162,10 @@ def add_refresh_prompt_task(self, key: str, fetch_func: Callable[[], None]) -> N self._log.debug(f"Submitting refresh task for key: {key}") self._task_manager.add_task(key, fetch_func) + def clear(self) -> None: + """Clear the entire prompt cache, removing all cached prompts.""" + self._cache.clear() + @staticmethod def generate_cache_key( name: str, *, version: Optional[int], label: Optional[str] diff --git a/tests/test_prompt.py b/tests/test_prompt.py index d3c20d285..88b18f625 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -679,7 +679,11 @@ def test_prompt_end_to_end(): @pytest.fixture def langfuse(): - langfuse_instance = Langfuse() + langfuse_instance = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + host="https://mock-host.com", + ) langfuse_instance.api = Mock() return langfuse_instance @@ -1410,3 +1414,50 @@ def test_update_prompt(): expected_labels = sorted(["latest", "doe", "production", "john"]) assert sorted(fetched_prompt.labels) == expected_labels assert sorted(updated_prompt.labels) == expected_labels + + +def test_clear_prompt_cache(langfuse): + """Test clearing the entire prompt cache.""" + prompt_name = create_uuid() + + # Mock the API calls + mock_prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="test prompt", + type="text", + labels=["production"], + config={}, + tags=[], + ) + + # Mock the create_prompt API call + langfuse.api.prompts.create.return_value = mock_prompt + + # Mock the get_prompt API call + langfuse.api.prompts.get.return_value = mock_prompt + + # Create a prompt and cache it + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + ) + + # Get the prompt to cache it + cached_prompt = langfuse.get_prompt(prompt_name) + assert cached_prompt.name == prompt_name + + # Verify that the prompt is in the cache + cache_key = f"{prompt_name}-label:production" + assert langfuse._resources.prompt_cache.get(cache_key) is not None, "Prompt should be in cache before clearing" + + # Clear the entire prompt cache + langfuse.clear_prompt_cache() + + # Verify cache is completely cleared + assert langfuse._resources.prompt_cache.get(cache_key) is None, "Prompt should be removed from cache after clearing" + + # Verify data integrity + assert prompt_client.name == prompt_name + assert cached_prompt.name == prompt_name \ No newline at end of file From c514628ea4a1ca158e6de8b6922032acc99089db Mon Sep 17 00:00:00 2001 From: Cem Asma Date: Tue, 2 Sep 2025 13:23:40 +0300 Subject: [PATCH 042/296] feat(prompts): optional override for default prompt cache TTL (#1249) --- langfuse/_client/environment_variables.py | 10 ++ langfuse/_utils/prompt_cache.py | 6 +- tests/test_prompt.py | 182 +++++++++++++++++++++- 3 files changed, 196 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/environment_variables.py b/langfuse/_client/environment_variables.py index 4394d2077..262fee4d4 100644 --- a/langfuse/_client/environment_variables.py +++ b/langfuse/_client/environment_variables.py @@ -128,3 +128,13 @@ **Default value**: ``5`` """ + +LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS = "LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS" +""" +.. envvar: LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS + +Controls the default time-to-live (TTL) in seconds for cached prompts. +This setting determines how long prompt responses are cached before they expire. + +**Default value**: ``60`` +""" diff --git a/langfuse/_utils/prompt_cache.py b/langfuse/_utils/prompt_cache.py index 99abb759f..acca515cc 100644 --- a/langfuse/_utils/prompt_cache.py +++ b/langfuse/_utils/prompt_cache.py @@ -6,10 +6,14 @@ from queue import Empty, Queue from threading import Thread from typing import Callable, Dict, List, Optional, Set +import os from langfuse.model import PromptClient +from langfuse._client.environment_variables import ( + LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS +) -DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60 +DEFAULT_PROMPT_CACHE_TTL_SECONDS = int(os.getenv(LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS, 60)) DEFAULT_PROMPT_CACHE_REFRESH_WORKERS = 1 diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 88b18f625..2cff02f7b 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -13,6 +13,8 @@ from langfuse.model import ChatPromptClient, TextPromptClient from tests.utils import create_uuid, get_api +import os +from langfuse._client.environment_variables import LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS def test_create_prompt(): langfuse = Langfuse() @@ -688,6 +690,16 @@ def langfuse(): return langfuse_instance +@pytest.fixture +def langfuse_with_override_default_cache(): + langfuse_instance = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + host="https://mock-host.com", + default_cache_ttl_seconds=OVERRIDE_DEFAULT_PROMPT_CACHE_TTL_SECONDS, + ) + langfuse_instance.api = Mock() + return langfuse_instance # Fetching a new prompt when nothing in cache def test_get_fresh_prompt(langfuse): @@ -1416,6 +1428,174 @@ def test_update_prompt(): assert sorted(updated_prompt.labels) == expected_labels +def test_environment_variable_override_prompt_cache_ttl(): + """Test that LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS environment variable overrides default TTL.""" + import os + from unittest.mock import patch + + # Set environment variable to override default TTL + os.environ[LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS] = "120" + + # Create a new Langfuse instance to pick up the environment variable + langfuse = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + host="https://mock-host.com", + ) + langfuse.api = Mock() + + prompt_name = "test_env_override_ttl" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Test prompt with env override", + type="text", + labels=[], + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + # Mock time to control cache expiration + with patch.object(PromptCacheItem, "get_epoch_seconds") as mock_time: + mock_time.return_value = 0 + + # First call - should cache the prompt + result1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result1 == prompt_client + + # Check that prompt is cached + cached_item = langfuse._resources.prompt_cache.get( + langfuse._resources.prompt_cache.generate_cache_key(prompt_name, version=None, label=None) + ) + assert cached_item is not None + assert cached_item.value == prompt_client + + # Debug: check the cache item's expiry time + print(f"DEBUG: Cache item expiry: {cached_item._expiry}") + print(f"DEBUG: Current mock time: {mock_time.return_value}") + print(f"DEBUG: Is expired? {cached_item.is_expired()}") + + # Set time to 60 seconds (before new TTL of 120 seconds) + mock_time.return_value = 60 + + # Second call - should still use cache + result2 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 # No new server call + assert result2 == prompt_client + + # Set time to 120 seconds (at TTL expiration) + mock_time.return_value = 120 + + # Third call - should still use cache (stale cache behavior) + result3 = langfuse.get_prompt(prompt_name) + assert result3 == prompt_client + + # Wait for background refresh to complete + while True: + if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: + break + sleep(0.1) + + # Should have made a new server call for refresh + assert mock_server_call.call_count == 2 + + # Set time to 121 seconds (after TTL expiration) + mock_time.return_value = 121 + + # Fourth call - should use refreshed cache + result4 = langfuse.get_prompt(prompt_name) + assert result4 == prompt_client + + # Clean up environment variable + if LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS in os.environ: + del os.environ[LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS] + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_default_ttl_when_environment_variable_not_set(mock_time): + """Test that default 60-second TTL is used when LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS is not set.""" + from unittest.mock import patch + + # Ensure environment variable is not set + if LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS in os.environ: + del os.environ[LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS] + + # Set initial time to 0 + mock_time.return_value = 0 + + # Create a new Langfuse instance to pick up the default TTL + langfuse = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + host="https://mock-host.com", + ) + langfuse.api = Mock() + + prompt_name = "test_default_ttl" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Test prompt with default TTL", + type="text", + labels=[], + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + # First call - should cache the prompt + result1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result1 == prompt_client + + # Check that prompt is cached + cached_item = langfuse._resources.prompt_cache.get( + langfuse._resources.prompt_cache.generate_cache_key(prompt_name, version=None, label=None) + ) + assert cached_item is not None + assert cached_item.value == prompt_client + + # Set time to just before default TTL expiration + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS - 1 + + # Second call - should still use cache + result2 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 # No new server call + assert result2 == prompt_client + + # Set time to just after default TTL expiration to trigger cache expiry + # Use the actual DEFAULT_PROMPT_CACHE_TTL_SECONDS value that was imported + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 + + # Third call - should still use cache (stale cache behavior) + result3 = langfuse.get_prompt(prompt_name) + assert result3 == prompt_client + + # Wait for background refresh to complete + while True: + if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: + break + sleep(0.1) + + # Should have made a new server call for refresh + assert mock_server_call.call_count == 2 + + # Set time to just after default TTL expiration + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 + + # Fourth call - should use refreshed cache + result4 = langfuse.get_prompt(prompt_name) + assert result4 == prompt_client + + def test_clear_prompt_cache(langfuse): """Test clearing the entire prompt cache.""" prompt_name = create_uuid() @@ -1460,4 +1640,4 @@ def test_clear_prompt_cache(langfuse): # Verify data integrity assert prompt_client.name == prompt_name - assert cached_prompt.name == prompt_name \ No newline at end of file + assert cached_prompt.name == prompt_name From a23a34fe9cefa41dceedaa5ec8be8d040bc0b638 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:02:06 +0200 Subject: [PATCH 043/296] fix(langchain): parse tool_calls from AIMessage (#1316) --- langfuse/_client/client.py | 39 +++-- langfuse/_utils/prompt_cache.py | 10 +- langfuse/langchain/CallbackHandler.py | 10 +- tests/test_prompt.py | 233 +------------------------- 4 files changed, 35 insertions(+), 257 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 5655dd7ce..df243e51c 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -5,9 +5,9 @@ import logging import os -import warnings import re import urllib.parse +import warnings from datetime import datetime from hashlib import sha256 from time import time_ns @@ -17,8 +17,8 @@ List, Literal, Optional, - Union, Type, + Union, cast, overload, ) @@ -36,6 +36,13 @@ from packaging.version import Version from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.constants import ( + ObservationTypeGenerationLike, + ObservationTypeLiteral, + ObservationTypeLiteralNoEvent, + ObservationTypeSpanLike, + get_observation_types_list, +) from langfuse._client.datasets import DatasetClient, DatasetItemClient from langfuse._client.environment_variables import ( LANGFUSE_DEBUG, @@ -47,25 +54,18 @@ LANGFUSE_TRACING_ENABLED, LANGFUSE_TRACING_ENVIRONMENT, ) -from langfuse._client.constants import ( - ObservationTypeLiteral, - ObservationTypeLiteralNoEvent, - ObservationTypeGenerationLike, - ObservationTypeSpanLike, - get_observation_types_list, -) from langfuse._client.resource_manager import LangfuseResourceManager from langfuse._client.span import ( - LangfuseEvent, - LangfuseGeneration, - LangfuseSpan, LangfuseAgent, - LangfuseTool, LangfuseChain, - LangfuseRetriever, - LangfuseEvaluator, LangfuseEmbedding, + LangfuseEvaluator, + LangfuseEvent, + LangfuseGeneration, LangfuseGuardrail, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, ) from langfuse._utils import _get_timestamp from langfuse._utils.parse_error import handle_fern_exception @@ -2996,11 +2996,10 @@ def _url_encode(self, url: str, *, is_url_param: Optional[bool] = False) -> str: # we need add safe="" to force escaping of slashes # This is necessary for prompts in prompt folders return urllib.parse.quote(url, safe="") - - def clear_prompt_cache(self): - """ - Clear the entire prompt cache, removing all cached prompts. - + + def clear_prompt_cache(self) -> None: + """Clear the entire prompt cache, removing all cached prompts. + This method is useful when you want to force a complete refresh of all cached prompts, for example after major updates or when you need to ensure the latest versions are fetched from the server. diff --git a/langfuse/_utils/prompt_cache.py b/langfuse/_utils/prompt_cache.py index acca515cc..919333b6b 100644 --- a/langfuse/_utils/prompt_cache.py +++ b/langfuse/_utils/prompt_cache.py @@ -2,18 +2,20 @@ import atexit import logging +import os from datetime import datetime from queue import Empty, Queue from threading import Thread from typing import Callable, Dict, List, Optional, Set -import os -from langfuse.model import PromptClient from langfuse._client.environment_variables import ( - LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS + LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS, ) +from langfuse.model import PromptClient -DEFAULT_PROMPT_CACHE_TTL_SECONDS = int(os.getenv(LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS, 60)) +DEFAULT_PROMPT_CACHE_TTL_SECONDS = int( + os.getenv(LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS, 60) +) DEFAULT_PROMPT_CACHE_REFRESH_WORKERS = 1 diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 0c4fb5c2d..20bfa2534 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -939,9 +939,17 @@ def __join_tags_and_metadata( def _convert_message_to_dict(self, message: BaseMessage) -> Dict[str, Any]: # assistant message if isinstance(message, HumanMessage): - message_dict = {"role": "user", "content": message.content} + message_dict: Dict[str, Any] = {"role": "user", "content": message.content} elif isinstance(message, AIMessage): message_dict = {"role": "assistant", "content": message.content} + + if ( + hasattr(message, "tool_calls") + and message.tool_calls is not None + and len(message.tool_calls) > 0 + ): + message_dict["tool_calls"] = message.tool_calls + elif isinstance(message, SystemMessage): message_dict = {"role": "system", "content": message.content} elif isinstance(message, ToolMessage): diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 2cff02f7b..d3c20d285 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -13,8 +13,6 @@ from langfuse.model import ChatPromptClient, TextPromptClient from tests.utils import create_uuid, get_api -import os -from langfuse._client.environment_variables import LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS def test_create_prompt(): langfuse = Langfuse() @@ -681,25 +679,11 @@ def test_prompt_end_to_end(): @pytest.fixture def langfuse(): - langfuse_instance = Langfuse( - public_key="test-public-key", - secret_key="test-secret-key", - host="https://mock-host.com", - ) + langfuse_instance = Langfuse() langfuse_instance.api = Mock() return langfuse_instance -@pytest.fixture -def langfuse_with_override_default_cache(): - langfuse_instance = Langfuse( - public_key="test-public-key", - secret_key="test-secret-key", - host="https://mock-host.com", - default_cache_ttl_seconds=OVERRIDE_DEFAULT_PROMPT_CACHE_TTL_SECONDS, - ) - langfuse_instance.api = Mock() - return langfuse_instance # Fetching a new prompt when nothing in cache def test_get_fresh_prompt(langfuse): @@ -1426,218 +1410,3 @@ def test_update_prompt(): expected_labels = sorted(["latest", "doe", "production", "john"]) assert sorted(fetched_prompt.labels) == expected_labels assert sorted(updated_prompt.labels) == expected_labels - - -def test_environment_variable_override_prompt_cache_ttl(): - """Test that LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS environment variable overrides default TTL.""" - import os - from unittest.mock import patch - - # Set environment variable to override default TTL - os.environ[LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS] = "120" - - # Create a new Langfuse instance to pick up the environment variable - langfuse = Langfuse( - public_key="test-public-key", - secret_key="test-secret-key", - host="https://mock-host.com", - ) - langfuse.api = Mock() - - prompt_name = "test_env_override_ttl" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Test prompt with env override", - type="text", - labels=[], - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - # Mock time to control cache expiration - with patch.object(PromptCacheItem, "get_epoch_seconds") as mock_time: - mock_time.return_value = 0 - - # First call - should cache the prompt - result1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result1 == prompt_client - - # Check that prompt is cached - cached_item = langfuse._resources.prompt_cache.get( - langfuse._resources.prompt_cache.generate_cache_key(prompt_name, version=None, label=None) - ) - assert cached_item is not None - assert cached_item.value == prompt_client - - # Debug: check the cache item's expiry time - print(f"DEBUG: Cache item expiry: {cached_item._expiry}") - print(f"DEBUG: Current mock time: {mock_time.return_value}") - print(f"DEBUG: Is expired? {cached_item.is_expired()}") - - # Set time to 60 seconds (before new TTL of 120 seconds) - mock_time.return_value = 60 - - # Second call - should still use cache - result2 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 # No new server call - assert result2 == prompt_client - - # Set time to 120 seconds (at TTL expiration) - mock_time.return_value = 120 - - # Third call - should still use cache (stale cache behavior) - result3 = langfuse.get_prompt(prompt_name) - assert result3 == prompt_client - - # Wait for background refresh to complete - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - # Should have made a new server call for refresh - assert mock_server_call.call_count == 2 - - # Set time to 121 seconds (after TTL expiration) - mock_time.return_value = 121 - - # Fourth call - should use refreshed cache - result4 = langfuse.get_prompt(prompt_name) - assert result4 == prompt_client - - # Clean up environment variable - if LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS in os.environ: - del os.environ[LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS] - - -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_default_ttl_when_environment_variable_not_set(mock_time): - """Test that default 60-second TTL is used when LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS is not set.""" - from unittest.mock import patch - - # Ensure environment variable is not set - if LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS in os.environ: - del os.environ[LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS] - - # Set initial time to 0 - mock_time.return_value = 0 - - # Create a new Langfuse instance to pick up the default TTL - langfuse = Langfuse( - public_key="test-public-key", - secret_key="test-secret-key", - host="https://mock-host.com", - ) - langfuse.api = Mock() - - prompt_name = "test_default_ttl" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Test prompt with default TTL", - type="text", - labels=[], - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - # First call - should cache the prompt - result1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result1 == prompt_client - - # Check that prompt is cached - cached_item = langfuse._resources.prompt_cache.get( - langfuse._resources.prompt_cache.generate_cache_key(prompt_name, version=None, label=None) - ) - assert cached_item is not None - assert cached_item.value == prompt_client - - # Set time to just before default TTL expiration - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS - 1 - - # Second call - should still use cache - result2 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 # No new server call - assert result2 == prompt_client - - # Set time to just after default TTL expiration to trigger cache expiry - # Use the actual DEFAULT_PROMPT_CACHE_TTL_SECONDS value that was imported - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 - - # Third call - should still use cache (stale cache behavior) - result3 = langfuse.get_prompt(prompt_name) - assert result3 == prompt_client - - # Wait for background refresh to complete - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - # Should have made a new server call for refresh - assert mock_server_call.call_count == 2 - - # Set time to just after default TTL expiration - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 - - # Fourth call - should use refreshed cache - result4 = langfuse.get_prompt(prompt_name) - assert result4 == prompt_client - - -def test_clear_prompt_cache(langfuse): - """Test clearing the entire prompt cache.""" - prompt_name = create_uuid() - - # Mock the API calls - mock_prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="test prompt", - type="text", - labels=["production"], - config={}, - tags=[], - ) - - # Mock the create_prompt API call - langfuse.api.prompts.create.return_value = mock_prompt - - # Mock the get_prompt API call - langfuse.api.prompts.get.return_value = mock_prompt - - # Create a prompt and cache it - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt="test prompt", - labels=["production"], - ) - - # Get the prompt to cache it - cached_prompt = langfuse.get_prompt(prompt_name) - assert cached_prompt.name == prompt_name - - # Verify that the prompt is in the cache - cache_key = f"{prompt_name}-label:production" - assert langfuse._resources.prompt_cache.get(cache_key) is not None, "Prompt should be in cache before clearing" - - # Clear the entire prompt cache - langfuse.clear_prompt_cache() - - # Verify cache is completely cleared - assert langfuse._resources.prompt_cache.get(cache_key) is None, "Prompt should be removed from cache after clearing" - - # Verify data integrity - assert prompt_client.name == prompt_name - assert cached_prompt.name == prompt_name From 0854815367521d20177c9aee3dfb36df1a56c3d2 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:02:09 +0200 Subject: [PATCH 044/296] fix(openai): openrouter non-streamed cost tracking (#1319) --- langfuse/openai.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/langfuse/openai.py b/langfuse/openai.py index 7dea644d9..c14f7f1fb 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -791,6 +791,9 @@ def _wrap( model=model, output=completion, usage_details=usage, + cost_details=_parse_cost(openai_response.usage) + if hasattr(openai_response, "usage") + else None, ).end() return openai_response @@ -855,6 +858,9 @@ async def _wrap_async( output=completion, usage=usage, # backward compat for all V2 self hosters usage_details=usage, + cost_details=_parse_cost(openai_response.usage) + if hasattr(openai_response, "usage") + else None, ).end() return openai_response From a4d5d72363591c36df3e55d86f7ed06ff3d3859a Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:02:31 +0200 Subject: [PATCH 045/296] chore: release v3.3.4 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 1e27fd168..48df2a02e 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.3.3" +__version__ = "3.3.4" diff --git a/pyproject.toml b/pyproject.toml index 1dee4cac4..37ff24c6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.3.3" +version = "3.3.4" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From fed7240f8101d6d6afd048147e129b53102bd71b Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:28:55 +0200 Subject: [PATCH 046/296] fix(langchain): set cost to zero on errors (#1328) --- langfuse/langchain/CallbackHandler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 20bfa2534..b4c27c3bc 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -218,6 +218,7 @@ def on_retriever_error( level="ERROR", status_message=str(error), input=kwargs.get("inputs"), + cost_details={"total": 0}, ).end() except Exception as e: @@ -527,6 +528,7 @@ def on_chain_error( ), status_message=str(error) if level else None, input=kwargs.get("inputs"), + cost_details={"total": 0}, ).end() except Exception as e: @@ -733,6 +735,7 @@ def on_tool_error( status_message=str(error), level="ERROR", input=kwargs.get("inputs"), + cost_details={"total": 0}, ).end() except Exception as e: @@ -913,6 +916,7 @@ def on_llm_error( status_message=str(error), level="ERROR", input=kwargs.get("inputs"), + cost_details={"total": 0}, ).end() except Exception as e: From 017e4885cb61efcdc1420523119a0c9482b56362 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:17:03 +0200 Subject: [PATCH 047/296] fix(client): pass env var tracing environment to resource manager (#1331) --- langfuse/_client/client.py | 2 +- tests/test_openai.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index df243e51c..df0173303 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -257,7 +257,7 @@ def __init__( secret_key=secret_key, host=self._host, timeout=timeout, - environment=environment, + environment=self._environment, release=release, flush_at=flush_at, flush_interval=flush_interval, diff --git a/tests/test_openai.py b/tests/test_openai.py index 85205db28..623802e55 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -94,6 +94,7 @@ def test_openai_chat_completion_stream(openai): assert len(chat_content) > 0 langfuse.flush() + sleep(1) generation = get_api().observations.get_many( name=generation_name, type="GENERATION" From f153b592412a225ad2aaec07a0e3ac17e392d3d2 Mon Sep 17 00:00:00 2001 From: Zentorno Date: Tue, 16 Sep 2025 15:11:21 +0800 Subject: [PATCH 048/296] fix(openai): handle None arguments in tool calls (#1339) --- langfuse/openai.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/langfuse/openai.py b/langfuse/openai.py index c14f7f1fb..d7b0ecd85 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -641,6 +641,10 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: curr[-1]["name"] = curr[-1]["name"] or getattr( tool_call_chunk, "name", None ) + + if curr[-1]["arguments"] is None: + curr[-1]["arguments"] = "" + curr[-1]["arguments"] += getattr( tool_call_chunk, "arguments", None ) From bd1a5d381965cd90a2222b760222c384f1ce4b54 Mon Sep 17 00:00:00 2001 From: Nimar Date: Tue, 16 Sep 2025 15:07:59 +0200 Subject: [PATCH 049/296] fix(get_client): preserve properties on client such as environment (#1341) * fix(get_client): preserve properties on client such as environment * also preserve the other properties --- langfuse/_client/get_client.py | 36 ++++++++----- langfuse/_client/resource_manager.py | 11 ++++ tests/test_resource_manager.py | 79 ++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 tests/test_resource_manager.py diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index ff619095e..8bcdecd40 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -33,6 +33,28 @@ def _set_current_public_key(public_key: Optional[str]) -> Iterator[None]: _current_public_key.reset(token) +def _create_client_from_instance( + instance: "LangfuseResourceManager", public_key: Optional[str] = None +) -> Langfuse: + """Create a Langfuse client from a resource manager instance with all settings preserved.""" + return Langfuse( + public_key=public_key or instance.public_key, + secret_key=instance.secret_key, + host=instance.host, + tracing_enabled=instance.tracing_enabled, + environment=instance.environment, + timeout=instance.timeout, + flush_at=instance.flush_at, + flush_interval=instance.flush_interval, + release=instance.release, + media_upload_thread_count=instance.media_upload_thread_count, + sample_rate=instance.sample_rate, + mask=instance.mask, + blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes, + additional_headers=instance.additional_headers, + ) + + def get_client(*, public_key: Optional[str] = None) -> Langfuse: """Get or create a Langfuse client instance. @@ -93,12 +115,7 @@ def get_client(*, public_key: Optional[str] = None) -> Langfuse: # Initialize with the credentials bound to the instance # This is important if the original instance was instantiated # via constructor arguments - return Langfuse( - public_key=instance.public_key, - secret_key=instance.secret_key, - host=instance.host, - tracing_enabled=instance.tracing_enabled, - ) + return _create_client_from_instance(instance) else: # Multiple clients exist but no key specified - disable tracing @@ -126,9 +143,4 @@ def get_client(*, public_key: Optional[str] = None) -> Langfuse: ) # target_instance is guaranteed to be not None at this point - return Langfuse( - public_key=public_key, - secret_key=target_instance.secret_key, - host=target_instance.host, - tracing_enabled=target_instance.tracing_enabled, - ) + return _create_client_from_instance(target_instance, public_key) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index e0e3cbadc..70ed5b17c 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -162,6 +162,17 @@ def _initialize_instance( self.tracing_enabled = tracing_enabled self.host = host self.mask = mask + self.environment = environment + + # Store additional client settings for get_client() to use + self.timeout = timeout + self.flush_at = flush_at + self.flush_interval = flush_interval + self.release = release + self.media_upload_thread_count = media_upload_thread_count + self.sample_rate = sample_rate + self.blocked_instrumentation_scopes = blocked_instrumentation_scopes + self.additional_headers = additional_headers # OTEL Tracer if tracing_enabled: diff --git a/tests/test_resource_manager.py b/tests/test_resource_manager.py new file mode 100644 index 000000000..fa6eb56bf --- /dev/null +++ b/tests/test_resource_manager.py @@ -0,0 +1,79 @@ +"""Test the LangfuseResourceManager and get_client() function.""" + +from langfuse import Langfuse +from langfuse._client.get_client import get_client +from langfuse._client.resource_manager import LangfuseResourceManager + + +def test_get_client_preserves_all_settings(): + """Test that get_client() preserves environment and all client settings.""" + with LangfuseResourceManager._lock: + LangfuseResourceManager._instances.clear() + + settings = { + "environment": "test-env", + "release": "v1.2.3", + "timeout": 30, + "flush_at": 100, + "sample_rate": 0.8, + "additional_headers": {"X-Custom": "value"}, + } + + original_client = Langfuse(**settings) + retrieved_client = get_client() + + assert retrieved_client._environment == settings["environment"] + + assert retrieved_client._resources is not None + rm = retrieved_client._resources + assert rm.environment == settings["environment"] + assert rm.timeout == settings["timeout"] + assert rm.sample_rate == settings["sample_rate"] + assert rm.additional_headers == settings["additional_headers"] + + original_client.shutdown() + + +def test_get_client_multiple_clients_preserve_different_settings(): + """Test that get_client() preserves different settings for multiple clients.""" + # Settings for client A + settings_a = { + "public_key": "pk-comprehensive-a", + "secret_key": "sk-comprehensive-a", + "environment": "env-a", + "release": "release-a", + "timeout": 10, + "sample_rate": 0.5, + } + + # Settings for client B + settings_b = { + "public_key": "pk-comprehensive-b", + "secret_key": "sk-comprehensive-b", + "environment": "env-b", + "release": "release-b", + "timeout": 20, + "sample_rate": 0.9, + } + + client_a = Langfuse(**settings_a) + client_b = Langfuse(**settings_b) + + # Get clients via get_client() + retrieved_a = get_client(public_key="pk-comprehensive-a") + retrieved_b = get_client(public_key="pk-comprehensive-b") + + # Verify each client preserves its own settings + assert retrieved_a._environment == settings_a["environment"] + assert retrieved_b._environment == settings_b["environment"] + + if retrieved_a._resources and retrieved_b._resources: + assert retrieved_a._resources.timeout == settings_a["timeout"] + assert retrieved_b._resources.timeout == settings_b["timeout"] + assert retrieved_a._resources.sample_rate == settings_a["sample_rate"] + assert retrieved_b._resources.sample_rate == settings_b["sample_rate"] + assert retrieved_a._resources.release == settings_a["release"] + assert retrieved_b._resources.release == settings_b["release"] + + client_a.shutdown() + client_b.shutdown() From b5f5491812bbd57052f00d482fca34b896f5c7c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:16:47 +0000 Subject: [PATCH 050/296] chore(deps-dev): bump langgraph from 0.6.6 to 0.6.7 (#1327) Bumps [langgraph](https://github.com/langchain-ai/langgraph) from 0.6.6 to 0.6.7. - [Release notes](https://github.com/langchain-ai/langgraph/releases) - [Commits](https://github.com/langchain-ai/langgraph/compare/0.6.6...0.6.7) --- updated-dependencies: - dependency-name: langgraph dependency-version: 0.6.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3380643bd..41b0b421b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -714,14 +714,14 @@ langchain-core = ">=0.3.72,<1.0.0" [[package]] name = "langgraph" -version = "0.6.6" +version = "0.6.7" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "langgraph-0.6.6-py3-none-any.whl", hash = "sha256:a2283a5236abba6c8307c1a485c04e8a0f0ffa2be770878782a7bf2deb8d7954"}, - {file = "langgraph-0.6.6.tar.gz", hash = "sha256:e7d3cefacf356f8c01721b166b67b3bf581659d5361a3530f59ecd9b8448eca7"}, + {file = "langgraph-0.6.7-py3-none-any.whl", hash = "sha256:c724dd8c24806b70faf4903e8e20c0234f8c0a356e0e96a88035cbecca9df2cf"}, + {file = "langgraph-0.6.7.tar.gz", hash = "sha256:ba7fd17b8220142d6a4269b6038f2b3dcbcef42cd5ecf4a4c8d9b60b010830a6"}, ] [package.dependencies] From abf3628c4f92d8dee736a1a163cf54b1edec0cbf Mon Sep 17 00:00:00 2001 From: Anne Aguirre Date: Tue, 16 Sep 2025 15:59:41 +0100 Subject: [PATCH 051/296] fix(prompt-folders): update prompt in folder failed with wrong encoding (#1335) Hotfix for SDK Client update_prompt failing when prompts are in folders - name contains slash Co-authored-by: Nimar --- langfuse/_client/client.py | 2 +- tests/test_prompt.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index df0173303..669ce1828 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2974,7 +2974,7 @@ def update_prompt( """ updated_prompt = self.api.prompt_version.update( - name=name, + name=self._url_encode(name), version=version, new_labels=new_labels, ) diff --git a/tests/test_prompt.py b/tests/test_prompt.py index d3c20d285..e5346debf 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -1410,3 +1410,37 @@ def test_update_prompt(): expected_labels = sorted(["latest", "doe", "production", "john"]) assert sorted(fetched_prompt.labels) == expected_labels assert sorted(updated_prompt.labels) == expected_labels + + +def test_update_prompt_in_folder(): + langfuse = Langfuse() + prompt_name = f"some-folder/{create_uuid()}" + + # Create initial prompt + langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + ) + + old_prompt_obj = langfuse.get_prompt(prompt_name) + + updated_prompt = langfuse.update_prompt( + name=old_prompt_obj.name, + version=old_prompt_obj.version, + new_labels=["john", "doe"], + ) + + # Fetch prompt after update (should be invalidated) + fetched_prompt = langfuse.get_prompt(prompt_name) + + # Verify the fetched prompt matches the updated values + assert fetched_prompt.name == prompt_name + assert fetched_prompt.version == 1 + print(f"Fetched prompt labels: {fetched_prompt.labels}") + print(f"Updated prompt labels: {updated_prompt.labels}") + + # production was set by the first call, latest is managed and set by Langfuse + expected_labels = sorted(["latest", "doe", "production", "john"]) + assert sorted(fetched_prompt.labels) == expected_labels + assert sorted(updated_prompt.labels) == expected_labels From acd0e0d852312ae7701a7f4bda71d639ed36b3d5 Mon Sep 17 00:00:00 2001 From: Nimar Date: Tue, 16 Sep 2025 17:01:27 +0200 Subject: [PATCH 052/296] chore: release v3.3.5 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 48df2a02e..642004f62 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.3.4" +__version__ = "3.3.5" diff --git a/pyproject.toml b/pyproject.toml index 37ff24c6a..c2c6c95d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.3.4" +version = "3.3.5" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From a0ddc0047f1744ad32461fdc63c48d8cf534002c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:46:05 +0200 Subject: [PATCH 053/296] feat(experiments): add experiment runner (#1334) --- langfuse/__init__.py | 15 +- langfuse/_client/client.py | 448 +++++++++- langfuse/_client/datasets.py | 238 +++++- langfuse/_client/span.py | 26 +- langfuse/_client/utils.py | 69 +- langfuse/_task_manager/media_manager.py | 3 +- langfuse/experiment.py | 1046 +++++++++++++++++++++++ langfuse/types.py | 30 +- poetry.lock | 460 +++++++--- pyproject.toml | 1 + tests/test_core_sdk.py | 2 +- tests/test_experiments.py | 670 +++++++++++++++ tests/test_openai.py | 2 +- tests/test_utils.py | 254 ++++++ 14 files changed, 3119 insertions(+), 145 deletions(-) create mode 100644 langfuse/experiment.py create mode 100644 tests/test_experiments.py create mode 100644 tests/test_utils.py diff --git a/langfuse/__init__.py b/langfuse/__init__.py index 3449e851f..b2b73b54b 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -1,21 +1,23 @@ """.. include:: ../README.md""" +from langfuse.experiment import Evaluation + from ._client import client as _client_module from ._client.attributes import LangfuseOtelSpanAttributes from ._client.constants import ObservationTypeLiteral from ._client.get_client import get_client from ._client.observe import observe from ._client.span import ( - LangfuseEvent, - LangfuseGeneration, - LangfuseSpan, LangfuseAgent, - LangfuseTool, LangfuseChain, LangfuseEmbedding, LangfuseEvaluator, - LangfuseRetriever, + LangfuseEvent, + LangfuseGeneration, LangfuseGuardrail, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, ) Langfuse = _client_module.Langfuse @@ -36,4 +38,7 @@ "LangfuseEvaluator", "LangfuseRetriever", "LangfuseGuardrail", + "Evaluation", + "experiment", + "api", ] diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 669ce1828..ceb29c5d3 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -3,6 +3,7 @@ This module implements Langfuse's core observability functionality on top of the OpenTelemetry (OTel) standard. """ +import asyncio import logging import os import re @@ -13,6 +14,7 @@ from time import time_ns from typing import ( Any, + Callable, Dict, List, Literal, @@ -67,6 +69,7 @@ LangfuseSpan, LangfuseTool, ) +from langfuse._client.utils import run_async_safely from langfuse._utils import _get_timestamp from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache @@ -78,6 +81,18 @@ Prompt_Chat, Prompt_Text, ) +from langfuse.experiment import ( + Evaluation, + EvaluatorFunction, + ExperimentData, + ExperimentItem, + ExperimentItemResult, + ExperimentResult, + RunEvaluatorFunction, + TaskFunction, + _run_evaluator, + _run_task, +) from langfuse.logger import langfuse_logger from langfuse.media import LangfuseMedia from langfuse.model import ( @@ -86,6 +101,7 @@ ChatPromptClient, CreateDatasetItemRequest, CreateDatasetRequest, + CreateDatasetRunItemRequest, Dataset, DatasetItem, DatasetStatus, @@ -735,7 +751,7 @@ def start_generation( cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, ) -> LangfuseGeneration: - """[DEPRECATED] Create a new generation span for model generations. + """Create a new generation span for model generations. DEPRECATED: This method is deprecated and will be removed in a future version. Use start_observation(as_type='generation') instead. @@ -831,7 +847,7 @@ def start_as_current_generation( prompt: Optional[PromptClient] = None, end_on_exit: Optional[bool] = None, ) -> _AgnosticContextManager[LangfuseGeneration]: - """[DEPRECATED] Create a new generation span and set it as the current span in a context manager. + """Create a new generation span and set it as the current span in a context manager. DEPRECATED: This method is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='generation') instead. @@ -2444,6 +2460,434 @@ def get_dataset( handle_fern_exception(e) raise e + def run_experiment( + self, + *, + name: str, + run_name: Optional[str] = None, + description: Optional[str] = None, + data: ExperimentData, + task: TaskFunction, + evaluators: List[EvaluatorFunction] = [], + run_evaluators: List[RunEvaluatorFunction] = [], + max_concurrency: int = 50, + metadata: Optional[Dict[str, Any]] = None, + ) -> ExperimentResult: + """Run an experiment on a dataset with automatic tracing and evaluation. + + This method executes a task function on each item in the provided dataset, + automatically traces all executions with Langfuse for observability, runs + item-level and run-level evaluators on the outputs, and returns comprehensive + results with evaluation metrics. + + The experiment system provides: + - Automatic tracing of all task executions + - Concurrent processing with configurable limits + - Comprehensive error handling that isolates failures + - Integration with Langfuse datasets for experiment tracking + - Flexible evaluation framework supporting both sync and async evaluators + + Args: + name: Human-readable name for the experiment. Used for identification + in the Langfuse UI. + run_name: Optional exact name for the experiment run. If provided, this will be + used as the exact dataset run name if the `data` contains Langfuse dataset items. + If not provided, this will default to the experiment name appended with an ISO timestamp. + description: Optional description explaining the experiment's purpose, + methodology, or expected outcomes. + data: Array of data items to process. Can be either: + - List of dict-like items with 'input', 'expected_output', 'metadata' keys + - List of Langfuse DatasetItem objects from dataset.items + task: Function that processes each data item and returns output. + Must accept 'item' as keyword argument and can return sync or async results. + The task function signature should be: task(*, item, **kwargs) -> Any + evaluators: List of functions to evaluate each item's output individually. + Each evaluator receives input, output, expected_output, and metadata. + Can return single Evaluation dict or list of Evaluation dicts. + run_evaluators: List of functions to evaluate the entire experiment run. + Each run evaluator receives all item_results and can compute aggregate metrics. + Useful for calculating averages, distributions, or cross-item comparisons. + max_concurrency: Maximum number of concurrent task executions (default: 50). + Controls the number of items processed simultaneously. Adjust based on + API rate limits and system resources. + metadata: Optional metadata dictionary to attach to all experiment traces. + This metadata will be included in every trace created during the experiment. + If `data` are Langfuse dataset items, the metadata will be attached to the dataset run, too. + + Returns: + ExperimentResult containing: + - run_name: The experiment run name. This is equal to the dataset run name if experiment was on Langfuse dataset. + - item_results: List of results for each processed item with outputs and evaluations + - run_evaluations: List of aggregate evaluation results for the entire run + - dataset_run_id: ID of the dataset run (if using Langfuse datasets) + - dataset_run_url: Direct URL to view results in Langfuse UI (if applicable) + + Raises: + ValueError: If required parameters are missing or invalid + Exception: If experiment setup fails (individual item failures are handled gracefully) + + Examples: + Basic experiment with local data: + ```python + def summarize_text(*, item, **kwargs): + return f"Summary: {item['input'][:50]}..." + + def length_evaluator(*, input, output, expected_output=None, **kwargs): + return { + "name": "output_length", + "value": len(output), + "comment": f"Output contains {len(output)} characters" + } + + result = langfuse.run_experiment( + name="Text Summarization Test", + description="Evaluate summarization quality and length", + data=[ + {"input": "Long article text...", "expected_output": "Expected summary"}, + {"input": "Another article...", "expected_output": "Another summary"} + ], + task=summarize_text, + evaluators=[length_evaluator] + ) + + print(f"Processed {len(result.item_results)} items") + for item_result in result.item_results: + print(f"Input: {item_result.item['input']}") + print(f"Output: {item_result.output}") + print(f"Evaluations: {item_result.evaluations}") + ``` + + Advanced experiment with async task and multiple evaluators: + ```python + async def llm_task(*, item, **kwargs): + # Simulate async LLM call + response = await openai_client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": item["input"]}] + ) + return response.choices[0].message.content + + def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): + if expected_output and expected_output.lower() in output.lower(): + return {"name": "accuracy", "value": 1.0, "comment": "Correct answer"} + return {"name": "accuracy", "value": 0.0, "comment": "Incorrect answer"} + + def toxicity_evaluator(*, input, output, expected_output=None, **kwargs): + # Simulate toxicity check + toxicity_score = check_toxicity(output) # Your toxicity checker + return { + "name": "toxicity", + "value": toxicity_score, + "comment": f"Toxicity level: {'high' if toxicity_score > 0.7 else 'low'}" + } + + def average_accuracy(*, item_results, **kwargs): + accuracies = [ + eval.value for result in item_results + for eval in result.evaluations + if eval.name == "accuracy" + ] + return { + "name": "average_accuracy", + "value": sum(accuracies) / len(accuracies) if accuracies else 0, + "comment": f"Average accuracy across {len(accuracies)} items" + } + + result = langfuse.run_experiment( + name="LLM Safety and Accuracy Test", + description="Evaluate model accuracy and safety across diverse prompts", + data=test_dataset, # Your dataset items + task=llm_task, + evaluators=[accuracy_evaluator, toxicity_evaluator], + run_evaluators=[average_accuracy], + max_concurrency=5, # Limit concurrent API calls + metadata={"model": "gpt-4", "temperature": 0.7} + ) + ``` + + Using with Langfuse datasets: + ```python + # Get dataset from Langfuse + dataset = langfuse.get_dataset("my-eval-dataset") + + result = dataset.run_experiment( + name="Production Model Evaluation", + description="Monthly evaluation of production model performance", + task=my_production_task, + evaluators=[accuracy_evaluator, latency_evaluator] + ) + + # Results automatically linked to dataset in Langfuse UI + print(f"View results: {result['dataset_run_url']}") + ``` + + Note: + - Task and evaluator functions can be either synchronous or asynchronous + - Individual item failures are logged but don't stop the experiment + - All executions are automatically traced and visible in Langfuse UI + - When using Langfuse datasets, results are automatically linked for easy comparison + - This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.) + - Async execution is handled automatically with smart event loop detection + """ + return cast( + ExperimentResult, + run_async_safely( + self._run_experiment_async( + name=name, + run_name=self._create_experiment_run_name( + name=name, run_name=run_name + ), + description=description, + data=data, + task=task, + evaluators=evaluators or [], + run_evaluators=run_evaluators or [], + max_concurrency=max_concurrency, + metadata=metadata or {}, + ), + ), + ) + + async def _run_experiment_async( + self, + *, + name: str, + run_name: str, + description: Optional[str], + data: ExperimentData, + task: TaskFunction, + evaluators: List[EvaluatorFunction], + run_evaluators: List[RunEvaluatorFunction], + max_concurrency: int, + metadata: Dict[str, Any], + ) -> ExperimentResult: + langfuse_logger.debug( + f"Starting experiment '{name}' run '{run_name}' with {len(data)} items" + ) + + # Set up concurrency control + semaphore = asyncio.Semaphore(max_concurrency) + + # Process all items + async def process_item(item: ExperimentItem) -> ExperimentItemResult: + async with semaphore: + return await self._process_experiment_item( + item, task, evaluators, name, run_name, description, metadata + ) + + # Run all items concurrently + tasks = [process_item(item) for item in data] + item_results = await asyncio.gather(*tasks, return_exceptions=True) + + # Filter out any exceptions and log errors + valid_results: List[ExperimentItemResult] = [] + for i, result in enumerate(item_results): + if isinstance(result, Exception): + langfuse_logger.error(f"Item {i} failed: {result}") + elif isinstance(result, ExperimentItemResult): + valid_results.append(result) # type: ignore + + # Run experiment-level evaluators + run_evaluations: List[Evaluation] = [] + for run_evaluator in run_evaluators: + try: + evaluations = await _run_evaluator( + run_evaluator, item_results=valid_results + ) + run_evaluations.extend(evaluations) + except Exception as e: + langfuse_logger.error(f"Run evaluator failed: {e}") + + # Generate dataset run URL if applicable + dataset_run_id = valid_results[0].dataset_run_id if valid_results else None + dataset_run_url = None + if dataset_run_id and data: + try: + # Check if the first item has dataset_id (for DatasetItem objects) + first_item = data[0] + dataset_id = None + + if hasattr(first_item, "dataset_id"): + dataset_id = getattr(first_item, "dataset_id", None) + + if dataset_id: + project_id = self._get_project_id() + + if project_id: + dataset_run_url = f"{self._host}/project/{project_id}/datasets/{dataset_id}/runs/{dataset_run_id}" + + except Exception: + pass # URL generation is optional + + # Store run-level evaluations as scores + for evaluation in run_evaluations: + try: + if dataset_run_id: + self.create_score( + dataset_run_id=dataset_run_id, + name=evaluation.name or "", + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=evaluation.metadata, + data_type=evaluation.data_type, # type: ignore + ) + + except Exception as e: + langfuse_logger.error(f"Failed to store run evaluation: {e}") + + # Flush scores and traces + self.flush() + + return ExperimentResult( + name=name, + run_name=run_name, + description=description, + item_results=valid_results, + run_evaluations=run_evaluations, + dataset_run_id=dataset_run_id, + dataset_run_url=dataset_run_url, + ) + + async def _process_experiment_item( + self, + item: ExperimentItem, + task: Callable, + evaluators: List[Callable], + experiment_name: str, + experiment_run_name: str, + experiment_description: Optional[str], + experiment_metadata: Dict[str, Any], + ) -> ExperimentItemResult: + # Execute task with tracing + span_name = "experiment-item-run" + + with self.start_as_current_span(name=span_name) as span: + try: + output = await _run_task(task, item) + + input_data = ( + item.get("input") + if isinstance(item, dict) + else getattr(item, "input", None) + ) + + item_metadata: Dict[str, Any] = {} + + if isinstance(item, dict): + item_metadata = item.get("metadata", None) or {} + + final_metadata = { + "experiment_name": experiment_name, + "experiment_run_name": experiment_run_name, + **experiment_metadata, + } + + if ( + not isinstance(item, dict) + and hasattr(item, "dataset_id") + and hasattr(item, "id") + ): + final_metadata.update( + {"dataset_id": item.dataset_id, "dataset_item_id": item.id} + ) + + if isinstance(item_metadata, dict): + final_metadata.update(item_metadata) + + span.update( + input=input_data, + output=output, + metadata=final_metadata, + ) + + # Get trace ID for linking + trace_id = span.trace_id + dataset_run_id = None + + # Link to dataset run if this is a dataset item + if hasattr(item, "id") and hasattr(item, "dataset_id"): + try: + dataset_run_item = self.api.dataset_run_items.create( + request=CreateDatasetRunItemRequest( + runName=experiment_run_name, + runDescription=experiment_description, + metadata=experiment_metadata, + datasetItemId=item.id, # type: ignore + traceId=trace_id, + observationId=span.id, + ) + ) + + dataset_run_id = dataset_run_item.dataset_run_id + + except Exception as e: + langfuse_logger.error(f"Failed to create dataset run item: {e}") + + # Run evaluators + evaluations = [] + + for evaluator in evaluators: + try: + expected_output = None + + if isinstance(item, dict): + expected_output = item.get("expected_output") + elif hasattr(item, "expected_output"): + expected_output = item.expected_output + + eval_metadata: Optional[Dict[str, Any]] = None + + if isinstance(item, dict): + eval_metadata = item.get("metadata") + elif hasattr(item, "metadata"): + eval_metadata = item.metadata + + eval_results = await _run_evaluator( + evaluator, + input=input_data, + output=output, + expected_output=expected_output, + metadata=eval_metadata, + ) + evaluations.extend(eval_results) + + # Store evaluations as scores + for evaluation in eval_results: + self.create_score( + trace_id=trace_id, + name=evaluation.name, + value=evaluation.value or -1, + comment=evaluation.comment, + metadata=evaluation.metadata, + ) + + except Exception as e: + langfuse_logger.error(f"Evaluator failed: {e}") + + return ExperimentItemResult( + item=item, + output=output, + evaluations=evaluations, + trace_id=trace_id, + dataset_run_id=dataset_run_id, + ) + + except Exception as e: + span.update( + output=f"Error: {str(e)}", level="ERROR", status_message=str(e) + ) + raise e + + def _create_experiment_run_name( + self, *, name: Optional[str] = None, run_name: Optional[str] = None + ) -> str: + if run_name: + return run_name + + iso_timestamp = _get_timestamp().isoformat().replace("+00:00", "Z") + + return f"{name} - {iso_timestamp}" + def auth_check(self) -> bool: """Check if the provided credentials (public and secret key) are valid. diff --git a/langfuse/_client/datasets.py b/langfuse/_client/datasets.py index f06570e57..beb1248ba 100644 --- a/langfuse/_client/datasets.py +++ b/langfuse/_client/datasets.py @@ -1,10 +1,15 @@ import datetime as dt import logging -from .span import LangfuseSpan -from typing import TYPE_CHECKING, Any, Generator, List, Optional +from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional from opentelemetry.util._decorator import _agnosticcontextmanager +from langfuse.experiment import ( + EvaluatorFunction, + ExperimentResult, + RunEvaluatorFunction, + TaskFunction, +) from langfuse.model import ( CreateDatasetRunItemRequest, Dataset, @@ -12,6 +17,8 @@ DatasetStatus, ) +from .span import LangfuseSpan + if TYPE_CHECKING: from langfuse._client.client import Langfuse @@ -181,3 +188,230 @@ def __init__(self, dataset: Dataset, items: List[DatasetItemClient]): self.created_at = dataset.created_at self.updated_at = dataset.updated_at self.items = items + self._langfuse: Optional["Langfuse"] = None + + def _get_langfuse_client(self) -> Optional["Langfuse"]: + """Get the Langfuse client from the first item.""" + if self._langfuse is None and self.items: + self._langfuse = self.items[0].langfuse + return self._langfuse + + def run_experiment( + self, + *, + name: str, + run_name: Optional[str] = None, + description: Optional[str] = None, + task: TaskFunction, + evaluators: List[EvaluatorFunction] = [], + run_evaluators: List[RunEvaluatorFunction] = [], + max_concurrency: int = 50, + metadata: Optional[Dict[str, Any]] = None, + ) -> ExperimentResult: + """Run an experiment on this Langfuse dataset with automatic tracking. + + This is a convenience method that runs an experiment using all items in this + dataset. It automatically creates a dataset run in Langfuse for tracking and + comparison purposes, linking all experiment results to the dataset. + + Key benefits of using dataset.run_experiment(): + - Automatic dataset run creation and linking in Langfuse UI + - Built-in experiment tracking and versioning + - Easy comparison between different experiment runs + - Direct access to dataset items with their metadata and expected outputs + - Automatic URL generation for viewing results in Langfuse dashboard + + Args: + name: Human-readable name for the experiment run. This will be used as + the dataset run name in Langfuse for tracking and identification. + run_name: Optional exact name for the dataset run. If provided, this will be + used as the exact dataset run name in Langfuse. If not provided, this will + default to the experiment name appended with an ISO timestamp. + description: Optional description of the experiment's purpose, methodology, + or what you're testing. Appears in the Langfuse UI for context. + task: Function that processes each dataset item and returns output. + The function will receive DatasetItem objects with .input, .expected_output, + .metadata attributes. Signature should be: task(*, item, **kwargs) -> Any + evaluators: List of functions to evaluate each item's output individually. + These will have access to the item's expected_output for comparison. + run_evaluators: List of functions to evaluate the entire experiment run. + Useful for computing aggregate statistics across all dataset items. + max_concurrency: Maximum number of concurrent task executions (default: 50). + Adjust based on API rate limits and system resources. + metadata: Optional metadata to attach to the experiment run and all traces. + Will be combined with individual item metadata. + + Returns: + ExperimentResult object containing: + - name: The experiment name. + - run_name: The experiment run name (equivalent to the dataset run name). + - description: Optional experiment description. + - item_results: Results for each dataset item with outputs and evaluations. + - run_evaluations: Aggregate evaluation results for the entire run. + - dataset_run_id: ID of the created dataset run in Langfuse. + - dataset_run_url: Direct URL to view the experiment results in Langfuse UI. + + The result object provides a format() method for human-readable output: + ```python + result = dataset.run_experiment(...) + print(result.format()) # Summary view + print(result.format(include_item_results=True)) # Detailed view + ``` + + Raises: + ValueError: If the dataset has no items or no Langfuse client is available. + + Examples: + Basic dataset experiment: + ```python + dataset = langfuse.get_dataset("qa-evaluation-set") + + def answer_questions(*, item, **kwargs): + # item is a DatasetItem with .input, .expected_output, .metadata + question = item.input + return my_qa_system.answer(question) + + def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): + if not expected_output: + return {"name": "accuracy", "value": None, "comment": "No expected output"} + + is_correct = output.strip().lower() == expected_output.strip().lower() + return { + "name": "accuracy", + "value": 1.0 if is_correct else 0.0, + "comment": "Correct" if is_correct else "Incorrect" + } + + result = dataset.run_experiment( + name="QA System v2.0 Evaluation", + description="Testing improved QA system on curated question set", + task=answer_questions, + evaluators=[accuracy_evaluator] + ) + + print(f"Evaluated {len(result['item_results'])} questions") + print(f"View detailed results: {result['dataset_run_url']}") + ``` + + Advanced experiment with multiple evaluators and run-level analysis: + ```python + dataset = langfuse.get_dataset("content-generation-benchmark") + + async def generate_content(*, item, **kwargs): + prompt = item.input + response = await openai_client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": prompt}], + temperature=0.7 + ) + return response.choices[0].message.content + + def quality_evaluator(*, input, output, expected_output=None, metadata=None, **kwargs): + # Use metadata for context-aware evaluation + content_type = metadata.get("type", "general") if metadata else "general" + + # Basic quality checks + word_count = len(output.split()) + min_words = {"blog": 300, "tweet": 10, "summary": 100}.get(content_type, 50) + + return [ + { + "name": "word_count", + "value": word_count, + "comment": f"Generated {word_count} words" + }, + { + "name": "meets_length_requirement", + "value": word_count >= min_words, + "comment": f"{'Meets' if word_count >= min_words else 'Below'} minimum {min_words} words for {content_type}" + } + ] + + def content_diversity(*, item_results, **kwargs): + # Analyze diversity across all generated content + all_outputs = [result["output"] for result in item_results] + unique_words = set() + total_words = 0 + + for output in all_outputs: + words = output.lower().split() + unique_words.update(words) + total_words += len(words) + + diversity_ratio = len(unique_words) / total_words if total_words > 0 else 0 + + return { + "name": "vocabulary_diversity", + "value": diversity_ratio, + "comment": f"Used {len(unique_words)} unique words out of {total_words} total ({diversity_ratio:.2%} diversity)" + } + + result = dataset.run_experiment( + name="Content Generation Diversity Test", + description="Evaluating content quality and vocabulary diversity across different content types", + task=generate_content, + evaluators=[quality_evaluator], + run_evaluators=[content_diversity], + max_concurrency=3, # Limit API calls + metadata={"model": "gpt-4", "temperature": 0.7} + ) + + # Results are automatically linked to dataset in Langfuse + print(f"Experiment completed! View in Langfuse: {result['dataset_run_url']}") + + # Access individual results + for i, item_result in enumerate(result["item_results"]): + print(f"Item {i+1}: {item_result['evaluations']}") + ``` + + Comparing different model versions: + ```python + # Run multiple experiments on the same dataset for comparison + dataset = langfuse.get_dataset("model-benchmark") + + # Experiment 1: GPT-4 + result_gpt4 = dataset.run_experiment( + name="GPT-4 Baseline", + description="Baseline performance with GPT-4", + task=lambda *, item, **kwargs: gpt4_model.generate(item.input), + evaluators=[accuracy_evaluator, fluency_evaluator] + ) + + # Experiment 2: Custom model + result_custom = dataset.run_experiment( + name="Custom Model v1.2", + description="Testing our fine-tuned model", + task=lambda *, item, **kwargs: custom_model.generate(item.input), + evaluators=[accuracy_evaluator, fluency_evaluator] + ) + + # Both experiments are now visible in Langfuse for easy comparison + print("Compare results in Langfuse:") + print(f"GPT-4: {result_gpt4.dataset_run_url}") + print(f"Custom: {result_custom.dataset_run_url}") + ``` + + Note: + - All experiment results are automatically tracked in Langfuse as dataset runs + - Dataset items provide .input, .expected_output, and .metadata attributes + - Results can be easily compared across different experiment runs in the UI + - The dataset_run_url provides direct access to detailed results and analysis + - Failed items are handled gracefully and logged without stopping the experiment + - This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.) + - Async execution is handled automatically with smart event loop detection + """ + langfuse_client = self._get_langfuse_client() + if not langfuse_client: + raise ValueError("No Langfuse client available. Dataset items are empty.") + + return langfuse_client.run_experiment( + name=name, + run_name=run_name, + description=description, + data=self.items, + task=task, + evaluators=evaluators, + run_evaluators=run_evaluators, + max_concurrency=max_concurrency, + metadata=metadata, + ) diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 68c1e8c63..9fa9c7489 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -1468,19 +1468,19 @@ def start_as_current_generation( return self.start_as_current_observation( name=name, as_type="generation", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ) + input=input, + output=output, + metadata=metadata, + version=version, + level=level, + status_message=status_message, + completion_start_time=completion_start_time, + model=model, + model_parameters=model_parameters, + usage_details=usage_details, + cost_details=cost_details, + prompt=prompt, + ) def create_event( self, diff --git a/langfuse/_client/utils.py b/langfuse/_client/utils.py index dac7a3f1b..d34857ebd 100644 --- a/langfuse/_client/utils.py +++ b/langfuse/_client/utils.py @@ -1,10 +1,13 @@ """Utility functions for Langfuse OpenTelemetry integration. This module provides utility functions for working with OpenTelemetry spans, -including formatting and serialization of span data. +including formatting and serialization of span data, and async execution helpers. """ +import asyncio import json +import threading +from typing import Any, Coroutine from opentelemetry import trace as otel_trace_api from opentelemetry.sdk import util @@ -58,3 +61,67 @@ def span_formatter(span: ReadableSpan) -> str: ) + "\n" ) + + +class _RunAsyncThread(threading.Thread): + """Helper thread class for running async coroutines in a separate thread.""" + + def __init__(self, coro: Coroutine[Any, Any, Any]) -> None: + self.coro = coro + self.result: Any = None + self.exception: Exception | None = None + super().__init__() + + def run(self) -> None: + try: + self.result = asyncio.run(self.coro) + except Exception as e: + self.exception = e + + +def run_async_safely(coro: Coroutine[Any, Any, Any]) -> Any: + """Safely run an async coroutine, handling existing event loops. + + This function detects if there's already a running event loop and uses + a separate thread if needed to avoid the "asyncio.run() cannot be called + from a running event loop" error. This is particularly useful in environments + like Jupyter notebooks, FastAPI applications, or other async frameworks. + + Args: + coro: The coroutine to run + + Returns: + The result of the coroutine + + Raises: + Any exception raised by the coroutine + + Example: + ```python + # Works in both sync and async contexts + async def my_async_function(): + await asyncio.sleep(1) + return "done" + + result = run_async_safely(my_async_function()) + ``` + """ + try: + # Check if there's already a running event loop + loop = asyncio.get_running_loop() + except RuntimeError: + # No running loop, safe to use asyncio.run() + return asyncio.run(coro) + + if loop and loop.is_running(): + # There's a running loop, use a separate thread + thread = _RunAsyncThread(coro) + thread.start() + thread.join() + + if thread.exception: + raise thread.exception + return thread.result + else: + # Loop exists but not running, safe to use asyncio.run() + return asyncio.run(coro) diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index a36e3b8af..1a32e3d60 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -49,7 +49,6 @@ def process_next_media_upload(self) -> None: self._queue.task_done() except Empty: - self._log.debug("Queue: Media upload queue is empty, waiting for new jobs") pass except Exception as e: self._log.error( @@ -248,7 +247,7 @@ def _process_upload_media_job( headers = {"Content-Type": data["content_type"]} - # In self-hosted setups with GCP, do not add unsupported headers that fail the upload + # In self-hosted setups with GCP, do not add unsupported headers that fail the upload is_self_hosted_gcs_bucket = "storage.googleapis.com" in upload_url if not is_self_hosted_gcs_bucket: diff --git a/langfuse/experiment.py b/langfuse/experiment.py new file mode 100644 index 000000000..f4c913c37 --- /dev/null +++ b/langfuse/experiment.py @@ -0,0 +1,1046 @@ +"""Langfuse experiment functionality for running and evaluating tasks on datasets. + +This module provides the core experiment functionality for the Langfuse Python SDK, +allowing users to run experiments on datasets with automatic tracing, evaluation, +and result formatting. +""" + +import asyncio +import logging +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Dict, + List, + Optional, + Protocol, + TypedDict, + Union, +) + +from langfuse.api import ScoreDataType + +if TYPE_CHECKING: + from langfuse._client.datasets import DatasetItemClient + + +class LocalExperimentItem(TypedDict, total=False): + """Structure for local experiment data items (not from Langfuse datasets). + + This TypedDict defines the structure for experiment items when using local data + rather than Langfuse-hosted datasets. All fields are optional to provide + flexibility in data structure. + + Attributes: + input: The input data to pass to the task function. Can be any type that + your task function can process (string, dict, list, etc.). This is + typically the prompt, question, or data that your task will operate on. + expected_output: Optional expected/ground truth output for evaluation purposes. + Used by evaluators to assess correctness or quality. Can be None if + no ground truth is available. + metadata: Optional metadata dictionary containing additional context about + this specific item. Can include information like difficulty level, + category, source, or any other relevant attributes that evaluators + might use for context-aware evaluation. + + Examples: + Simple text processing item: + ```python + item: LocalExperimentItem = { + "input": "Summarize this article: ...", + "expected_output": "Expected summary...", + "metadata": {"difficulty": "medium", "category": "news"} + } + ``` + + Classification item: + ```python + item: LocalExperimentItem = { + "input": {"text": "This movie is great!", "context": "movie review"}, + "expected_output": "positive", + "metadata": {"dataset_source": "imdb", "confidence": 0.95} + } + ``` + + Minimal item with only input: + ```python + item: LocalExperimentItem = { + "input": "What is the capital of France?" + } + ``` + """ + + input: Any + expected_output: Any + metadata: Optional[Dict[str, Any]] + + +ExperimentItem = Union[LocalExperimentItem, "DatasetItemClient"] +"""Type alias for items that can be processed in experiments. + +Can be either: +- LocalExperimentItem: Dict-like items with 'input', 'expected_output', 'metadata' keys +- DatasetItemClient: Items from Langfuse datasets with .input, .expected_output, .metadata attributes +""" + +ExperimentData = Union[List[LocalExperimentItem], List["DatasetItemClient"]] +"""Type alias for experiment datasets. + +Represents the collection of items to process in an experiment. Can be either: +- List[LocalExperimentItem]: Local data items as dictionaries +- List[DatasetItemClient]: Items from a Langfuse dataset (typically from dataset.items) +""" + + +class Evaluation: + """Represents an evaluation result for an experiment item or an entire experiment run. + + This class provides a strongly-typed way to create evaluation results in evaluator functions. + Users must use keyword arguments when instantiating this class. + + Attributes: + name: Unique identifier for the evaluation metric. Should be descriptive + and consistent across runs (e.g., "accuracy", "bleu_score", "toxicity"). + Used for aggregation and comparison across experiment runs. + value: The evaluation score or result. Can be: + - Numeric (int/float): For quantitative metrics like accuracy (0.85), BLEU (0.42) + - String: For categorical results like "positive", "negative", "neutral" + - Boolean: For binary assessments like "passes_safety_check" + - None: When evaluation cannot be computed (missing data, API errors, etc.) + comment: Optional human-readable explanation of the evaluation result. + Useful for providing context, explaining scoring rationale, or noting + special conditions. Displayed in Langfuse UI for interpretability. + metadata: Optional structured metadata about the evaluation process. + Can include confidence scores, intermediate calculations, model versions, + or any other relevant technical details. + data_type: Optional score data type. Required if value is not NUMERIC. + One of NUMERIC, CATEGORICAL, or BOOLEAN. Defaults to NUMERIC. + config_id: Optional Langfuse score config ID. + + Examples: + Basic accuracy evaluation: + ```python + from langfuse import Evaluation + + def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): + if not expected_output: + return Evaluation(name="accuracy", value=None, comment="No expected output") + + is_correct = output.strip().lower() == expected_output.strip().lower() + return Evaluation( + name="accuracy", + value=1.0 if is_correct else 0.0, + comment="Correct answer" if is_correct else "Incorrect answer" + ) + ``` + + Multi-metric evaluator: + ```python + def comprehensive_evaluator(*, input, output, expected_output=None, **kwargs): + return [ + Evaluation(name="length", value=len(output), comment=f"Output length: {len(output)} chars"), + Evaluation(name="has_greeting", value="hello" in output.lower(), comment="Contains greeting"), + Evaluation( + name="quality", + value=0.85, + comment="High quality response", + metadata={"confidence": 0.92, "model": "gpt-4"} + ) + ] + ``` + + Categorical evaluation: + ```python + def sentiment_evaluator(*, input, output, **kwargs): + sentiment = analyze_sentiment(output) # Returns "positive", "negative", or "neutral" + return Evaluation( + name="sentiment", + value=sentiment, + comment=f"Response expresses {sentiment} sentiment", + data_type="CATEGORICAL" + ) + ``` + + Failed evaluation with error handling: + ```python + def external_api_evaluator(*, input, output, **kwargs): + try: + score = external_api.evaluate(output) + return Evaluation(name="external_score", value=score) + except Exception as e: + return Evaluation( + name="external_score", + value=None, + comment=f"API unavailable: {e}", + metadata={"error": str(e), "retry_count": 3} + ) + ``` + + Note: + All arguments must be passed as keywords. Positional arguments are not allowed + to ensure code clarity and prevent errors from argument reordering. + """ + + def __init__( + self, + *, + name: str, + value: Union[int, float, str, bool, None], + comment: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, + data_type: Optional[ScoreDataType] = None, + config_id: Optional[str] = None, + ): + """Initialize an Evaluation with the provided data. + + Args: + name: Unique identifier for the evaluation metric. + value: The evaluation score or result. + comment: Optional human-readable explanation of the result. + metadata: Optional structured metadata about the evaluation process. + data_type: Optional score data type (NUMERIC, CATEGORICAL, or BOOLEAN). + config_id: Optional Langfuse score config ID. + + Note: + All arguments must be provided as keywords. Positional arguments will raise a TypeError. + """ + self.name = name + self.value = value + self.comment = comment + self.metadata = metadata + self.data_type = data_type + self.config_id = config_id + + +class ExperimentItemResult: + """Result structure for individual experiment items. + + This class represents the complete result of processing a single item + during an experiment run, including the original input, task output, + evaluations, and tracing information. Users must use keyword arguments when instantiating this class. + + Attributes: + item: The original experiment item that was processed. Can be either + a dictionary with 'input', 'expected_output', and 'metadata' keys, + or a DatasetItemClient from Langfuse datasets. + output: The actual output produced by the task function for this item. + Can be any type depending on what your task function returns. + evaluations: List of evaluation results for this item. Each evaluation + contains a name, value, optional comment, and optional metadata. + trace_id: Optional Langfuse trace ID for this item's execution. Used + to link the experiment result with the detailed trace in Langfuse UI. + dataset_run_id: Optional dataset run ID if this item was part of a + Langfuse dataset. None for local experiments. + + Examples: + Accessing item result data: + ```python + result = langfuse.run_experiment(...) + for item_result in result.item_results: + print(f"Input: {item_result.item}") + print(f"Output: {item_result.output}") + print(f"Trace: {item_result.trace_id}") + + # Access evaluations + for evaluation in item_result.evaluations: + print(f"{evaluation.name}: {evaluation.value}") + ``` + + Working with different item types: + ```python + # Local experiment item (dict) + if isinstance(item_result.item, dict): + input_data = item_result.item["input"] + expected = item_result.item.get("expected_output") + + # Langfuse dataset item (object with attributes) + else: + input_data = item_result.item.input + expected = item_result.item.expected_output + ``` + + Note: + All arguments must be passed as keywords. Positional arguments are not allowed + to ensure code clarity and prevent errors from argument reordering. + """ + + def __init__( + self, + *, + item: ExperimentItem, + output: Any, + evaluations: List[Evaluation], + trace_id: Optional[str], + dataset_run_id: Optional[str], + ): + """Initialize an ExperimentItemResult with the provided data. + + Args: + item: The original experiment item that was processed. + output: The actual output produced by the task function for this item. + evaluations: List of evaluation results for this item. + trace_id: Optional Langfuse trace ID for this item's execution. + dataset_run_id: Optional dataset run ID if this item was part of a Langfuse dataset. + + Note: + All arguments must be provided as keywords. Positional arguments will raise a TypeError. + """ + self.item = item + self.output = output + self.evaluations = evaluations + self.trace_id = trace_id + self.dataset_run_id = dataset_run_id + + +class ExperimentResult: + """Complete result structure for experiment execution. + + This class encapsulates the complete results of running an experiment on a dataset, + including individual item results, aggregate run-level evaluations, and metadata + about the experiment execution. + + Attributes: + name: The name of the experiment as specified during execution. + run_name: The name of the current experiment run. + description: Optional description of the experiment's purpose or methodology. + item_results: List of results from processing each individual dataset item, + containing the original item, task output, evaluations, and trace information. + run_evaluations: List of aggregate evaluation results computed across all items, + such as average scores, statistical summaries, or cross-item analyses. + dataset_run_id: Optional ID of the dataset run in Langfuse (when using Langfuse datasets). + dataset_run_url: Optional direct URL to view the experiment results in Langfuse UI. + + Examples: + Basic usage with local dataset: + ```python + result = langfuse.run_experiment( + name="Capital Cities Test", + data=local_data, + task=generate_capital, + evaluators=[accuracy_check] + ) + + print(f"Processed {len(result.item_results)} items") + print(result.format()) # Human-readable summary + + # Access individual results + for item_result in result.item_results: + print(f"Input: {item_result.item}") + print(f"Output: {item_result.output}") + print(f"Scores: {item_result.evaluations}") + ``` + + Usage with Langfuse datasets: + ```python + dataset = langfuse.get_dataset("qa-eval-set") + result = dataset.run_experiment( + name="GPT-4 QA Evaluation", + task=answer_question, + evaluators=[relevance_check, accuracy_check] + ) + + # View in Langfuse UI + if result.dataset_run_url: + print(f"View detailed results: {result.dataset_run_url}") + ``` + + Formatted output: + ```python + # Get summary view + summary = result.format() + print(summary) + + # Get detailed view with individual items + detailed = result.format(include_item_results=True) + with open("experiment_report.txt", "w") as f: + f.write(detailed) + ``` + """ + + def __init__( + self, + *, + name: str, + run_name: str, + description: Optional[str], + item_results: List[ExperimentItemResult], + run_evaluations: List[Evaluation], + dataset_run_id: Optional[str] = None, + dataset_run_url: Optional[str] = None, + ): + """Initialize an ExperimentResult with the provided data. + + Args: + name: The name of the experiment. + run_name: The current experiment run name. + description: Optional description of the experiment. + item_results: List of results from processing individual dataset items. + run_evaluations: List of aggregate evaluation results for the entire run. + dataset_run_id: Optional ID of the dataset run (for Langfuse datasets). + dataset_run_url: Optional URL to view results in Langfuse UI. + """ + self.name = name + self.run_name = run_name + self.description = description + self.item_results = item_results + self.run_evaluations = run_evaluations + self.dataset_run_id = dataset_run_id + self.dataset_run_url = dataset_run_url + + def format(self, *, include_item_results: bool = False) -> str: + r"""Format the experiment result for human-readable display. + + Converts the experiment result into a nicely formatted string suitable for + console output, logging, or reporting. The output includes experiment overview, + aggregate statistics, and optionally individual item details. + + This method provides a comprehensive view of experiment performance including: + - Experiment metadata (name, description, item count) + - List of evaluation metrics used across items + - Average scores computed across all processed items + - Run-level evaluation results (aggregate metrics) + - Links to view detailed results in Langfuse UI (when available) + - Individual item details (when requested) + + Args: + include_item_results: Whether to include detailed results for each individual + item in the formatted output. When False (default), only shows aggregate + statistics and summary information. When True, includes input/output/scores + for every processed item, making the output significantly longer but more + detailed for debugging and analysis purposes. + + Returns: + A formatted multi-line string containing: + - Experiment name and description (if provided) + - Total number of items successfully processed + - List of all evaluation metrics that were applied + - Average scores across all items for each numeric metric + - Run-level evaluation results with comments + - Dataset run URL for viewing in Langfuse UI (if applicable) + - Individual item details including inputs, outputs, and scores (if requested) + + Examples: + Basic usage showing aggregate results only: + ```python + result = langfuse.run_experiment( + name="Capital Cities", + data=dataset, + task=generate_capital, + evaluators=[accuracy_evaluator] + ) + + print(result.format()) + # Output: + # ────────────────────────────────────────────────── + # 📊 Capital Cities + # 100 items + # Evaluations: + # • accuracy + # Average Scores: + # • accuracy: 0.850 + ``` + + Detailed output including all individual item results: + ```python + detailed_report = result.format(include_item_results=True) + print(detailed_report) + # Output includes each item: + # 1. Item 1: + # Input: What is the capital of France? + # Expected: Paris + # Actual: The capital of France is Paris. + # Scores: + # • accuracy: 1.000 + # 💭 Correct answer found + # [... continues for all items ...] + ``` + + Saving formatted results to file for reporting: + ```python + with open("experiment_report.txt", "w") as f: + f.write(result.format(include_item_results=True)) + + # Or create summary report + summary = result.format() # Aggregate view only + print(f"Experiment Summary:\\n{summary}") + ``` + + Integration with logging systems: + ```python + import logging + logger = logging.getLogger("experiments") + + # Log summary after experiment + logger.info(f"Experiment completed:\\n{result.format()}") + + # Log detailed results for failed experiments + if any(eval['value'] < threshold for eval in result.run_evaluations): + logger.warning(f"Poor performance detected:\\n{result.format(include_item_results=True)}") + ``` + """ + if not self.item_results: + return "No experiment results to display." + + output = "" + + # Individual results section + if include_item_results: + for i, result in enumerate(self.item_results): + output += f"\\n{i + 1}. Item {i + 1}:\\n" + + # Extract and display input + item_input = None + if isinstance(result.item, dict): + item_input = result.item.get("input") + elif hasattr(result.item, "input"): + item_input = result.item.input + + if item_input is not None: + output += f" Input: {_format_value(item_input)}\\n" + + # Extract and display expected output + expected_output = None + if isinstance(result.item, dict): + expected_output = result.item.get("expected_output") + elif hasattr(result.item, "expected_output"): + expected_output = result.item.expected_output + + if expected_output is not None: + output += f" Expected: {_format_value(expected_output)}\\n" + output += f" Actual: {_format_value(result.output)}\\n" + + # Display evaluation scores + if result.evaluations: + output += " Scores:\\n" + for evaluation in result.evaluations: + score = evaluation.value + if isinstance(score, (int, float)): + score = f"{score:.3f}" + output += f" • {evaluation.name}: {score}" + if evaluation.comment: + output += f"\\n 💭 {evaluation.comment}" + output += "\\n" + + # Display trace link if available + if result.trace_id: + output += f"\\n Trace ID: {result.trace_id}\\n" + else: + output += f"Individual Results: Hidden ({len(self.item_results)} items)\\n" + output += "💡 Set include_item_results=True to view them\\n" + + # Experiment overview section + output += f"\\n{'─' * 50}\\n" + output += f"🧪 Experiment: {self.name}" + output += f"\n📋 Run name: {self.run_name}" + if self.description: + output += f" - {self.description}" + + output += f"\\n{len(self.item_results)} items" + + # Collect unique evaluation names across all items + evaluation_names = set() + for result in self.item_results: + for evaluation in result.evaluations: + evaluation_names.add(evaluation.name) + + if evaluation_names: + output += "\\nEvaluations:" + for eval_name in evaluation_names: + output += f"\\n • {eval_name}" + output += "\\n" + + # Calculate and display average scores + if evaluation_names: + output += "\\nAverage Scores:" + for eval_name in evaluation_names: + scores = [] + for result in self.item_results: + for evaluation in result.evaluations: + if evaluation.name == eval_name and isinstance( + evaluation.value, (int, float) + ): + scores.append(evaluation.value) + + if scores: + avg = sum(scores) / len(scores) + output += f"\\n • {eval_name}: {avg:.3f}" + output += "\\n" + + # Display run-level evaluations + if self.run_evaluations: + output += "\\nRun Evaluations:" + for run_eval in self.run_evaluations: + score = run_eval.value + if isinstance(score, (int, float)): + score = f"{score:.3f}" + output += f"\\n • {run_eval.name}: {score}" + if run_eval.comment: + output += f"\\n 💭 {run_eval.comment}" + output += "\\n" + + # Add dataset run URL if available + if self.dataset_run_url: + output += f"\\n🔗 Dataset Run:\\n {self.dataset_run_url}" + + return output + + +class TaskFunction(Protocol): + """Protocol defining the interface for experiment task functions. + + Task functions are the core processing functions that operate on each item + in an experiment dataset. They receive an experiment item as input and + produce some output that will be evaluated. + + Task functions must: + - Accept 'item' as a keyword argument + - Return any type of output (will be passed to evaluators) + - Can be either synchronous or asynchronous + - Should handle their own errors gracefully (exceptions will be logged) + """ + + def __call__( + self, + *, + item: ExperimentItem, + **kwargs: Dict[str, Any], + ) -> Union[Any, Awaitable[Any]]: + """Execute the task on an experiment item. + + This method defines the core processing logic for each item in your experiment. + The implementation should focus on the specific task you want to evaluate, + such as text generation, classification, summarization, etc. + + Args: + item: The experiment item to process. Can be either: + - Dict with keys like 'input', 'expected_output', 'metadata' + - Langfuse DatasetItem object with .input, .expected_output attributes + **kwargs: Additional keyword arguments that may be passed by the framework + + Returns: + Any: The output of processing the item. This output will be: + - Stored in the experiment results + - Passed to all item-level evaluators for assessment + - Traced automatically in Langfuse for observability + + Can return either a direct value or an awaitable (async) result. + + Examples: + Simple synchronous task: + ```python + def my_task(*, item, **kwargs): + prompt = f"Summarize: {item['input']}" + return my_llm_client.generate(prompt) + ``` + + Async task with error handling: + ```python + async def my_async_task(*, item, **kwargs): + try: + response = await openai_client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": item["input"]}] + ) + return response.choices[0].message.content + except Exception as e: + # Log error and return fallback + print(f"Task failed for item {item}: {e}") + return "Error: Could not process item" + ``` + + Task using dataset item attributes: + ```python + def classification_task(*, item, **kwargs): + # Works with both dict items and DatasetItem objects + text = item["input"] if isinstance(item, dict) else item.input + return classify_text(text) + ``` + """ + ... + + +class EvaluatorFunction(Protocol): + """Protocol defining the interface for item-level evaluator functions. + + Item-level evaluators assess the quality, correctness, or other properties + of individual task outputs. They receive the input, output, expected output, + and metadata for each item and return evaluation metrics. + + Evaluators should: + - Accept input, output, expected_output, and metadata as keyword arguments + - Return Evaluation dict(s) with 'name', 'value', 'comment', 'metadata' fields + - Be deterministic when possible for reproducible results + - Handle edge cases gracefully (missing expected output, malformed data, etc.) + - Can be either synchronous or asynchronous + """ + + def __call__( + self, + *, + input: Any, + output: Any, + expected_output: Any, + metadata: Optional[Dict[str, Any]], + **kwargs: Dict[str, Any], + ) -> Union[ + Evaluation, List[Evaluation], Awaitable[Union[Evaluation, List[Evaluation]]] + ]: + r"""Evaluate a task output for quality, correctness, or other metrics. + + This method should implement specific evaluation logic such as accuracy checking, + similarity measurement, toxicity detection, fluency assessment, etc. + + Args: + input: The original input that was passed to the task function. + This is typically the item['input'] or item.input value. + output: The output produced by the task function for this input. + This is the direct return value from your task function. + expected_output: The expected/ground truth output for comparison. + May be None if not available in the dataset. Evaluators should + handle this case appropriately. + metadata: Optional metadata from the experiment item that might + contain additional context for evaluation (categories, difficulty, etc.) + **kwargs: Additional keyword arguments that may be passed by the framework + + Returns: + Evaluation results in one of these formats: + - Single Evaluation dict: {"name": "accuracy", "value": 0.85, "comment": "..."} + - List of Evaluation dicts: [{"name": "precision", ...}, {"name": "recall", ...}] + - Awaitable returning either of the above (for async evaluators) + + Each Evaluation dict should contain: + - name (str): Unique identifier for this evaluation metric + - value (int|float|str|bool): The evaluation score or result + - comment (str, optional): Human-readable explanation of the result + - metadata (dict, optional): Additional structured data about the evaluation + + Examples: + Simple accuracy evaluator: + ```python + def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): + if expected_output is None: + return {"name": "accuracy", "value": None, "comment": "No expected output"} + + is_correct = output.strip().lower() == expected_output.strip().lower() + return { + "name": "accuracy", + "value": 1.0 if is_correct else 0.0, + "comment": "Exact match" if is_correct else "No match" + } + ``` + + Multi-metric evaluator: + ```python + def comprehensive_evaluator(*, input, output, expected_output=None, **kwargs): + results = [] + + # Length check + results.append({ + "name": "output_length", + "value": len(output), + "comment": f"Output contains {len(output)} characters" + }) + + # Sentiment analysis + sentiment_score = analyze_sentiment(output) + results.append({ + "name": "sentiment", + "value": sentiment_score, + "comment": f"Sentiment score: {sentiment_score:.2f}" + }) + + return results + ``` + + Async evaluator using external API: + ```python + async def llm_judge_evaluator(*, input, output, expected_output=None, **kwargs): + prompt = f"Rate the quality of this response on a scale of 1-10:\n" + prompt += f"Question: {input}\nResponse: {output}" + + response = await openai_client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": prompt}] + ) + + try: + score = float(response.choices[0].message.content.strip()) + return { + "name": "llm_judge_quality", + "value": score, + "comment": f"LLM judge rated this {score}/10" + } + except ValueError: + return { + "name": "llm_judge_quality", + "value": None, + "comment": "Could not parse LLM judge score" + } + ``` + + Context-aware evaluator: + ```python + def context_evaluator(*, input, output, metadata=None, **kwargs): + # Use metadata for context-specific evaluation + difficulty = metadata.get("difficulty", "medium") if metadata else "medium" + + # Adjust expectations based on difficulty + min_length = {"easy": 50, "medium": 100, "hard": 150}[difficulty] + + meets_requirement = len(output) >= min_length + return { + "name": f"meets_{difficulty}_requirement", + "value": meets_requirement, + "comment": f"Output {'meets' if meets_requirement else 'fails'} {difficulty} length requirement" + } + ``` + """ + ... + + +class RunEvaluatorFunction(Protocol): + """Protocol defining the interface for run-level evaluator functions. + + Run-level evaluators assess aggregate properties of the entire experiment run, + computing metrics that span across all items rather than individual outputs. + They receive the complete results from all processed items and can compute + statistics like averages, distributions, correlations, or other aggregate metrics. + + Run evaluators should: + - Accept item_results as a keyword argument containing all item results + - Return Evaluation dict(s) with aggregate metrics + - Handle cases where some items may have failed processing + - Compute meaningful statistics across the dataset + - Can be either synchronous or asynchronous + """ + + def __call__( + self, + *, + item_results: List[ExperimentItemResult], + **kwargs: Dict[str, Any], + ) -> Union[ + Evaluation, List[Evaluation], Awaitable[Union[Evaluation, List[Evaluation]]] + ]: + r"""Evaluate the entire experiment run with aggregate metrics. + + This method should implement aggregate evaluation logic such as computing + averages, calculating distributions, finding correlations, detecting patterns + across items, or performing statistical analysis on the experiment results. + + Args: + item_results: List of results from all successfully processed experiment items. + Each item result contains: + - item: The original experiment item + - output: The task function's output for this item + - evaluations: List of item-level evaluation results + - trace_id: Langfuse trace ID for this execution + - dataset_run_id: Dataset run ID (if using Langfuse datasets) + + Note: This list only includes items that were successfully processed. + Failed items are excluded but logged separately. + **kwargs: Additional keyword arguments that may be passed by the framework + + Returns: + Evaluation results in one of these formats: + - Single Evaluation dict: {"name": "avg_accuracy", "value": 0.78, "comment": "..."} + - List of Evaluation dicts: [{"name": "mean", ...}, {"name": "std_dev", ...}] + - Awaitable returning either of the above (for async evaluators) + + Each Evaluation dict should contain: + - name (str): Unique identifier for this run-level metric + - value (int|float|str|bool): The aggregate evaluation result + - comment (str, optional): Human-readable explanation of the metric + - metadata (dict, optional): Additional structured data about the evaluation + + Examples: + Average accuracy calculator: + ```python + def average_accuracy(*, item_results, **kwargs): + if not item_results: + return {"name": "avg_accuracy", "value": 0.0, "comment": "No results"} + + accuracy_values = [] + for result in item_results: + for evaluation in result.evaluations: + if evaluation.name == "accuracy": + accuracy_values.append(evaluation.value) + + if not accuracy_values: + return {"name": "avg_accuracy", "value": None, "comment": "No accuracy evaluations found"} + + avg = sum(accuracy_values) / len(accuracy_values) + return { + "name": "avg_accuracy", + "value": avg, + "comment": f"Average accuracy across {len(accuracy_values)} items: {avg:.2%}" + } + ``` + + Multiple aggregate metrics: + ```python + def statistical_summary(*, item_results, **kwargs): + if not item_results: + return [] + + results = [] + + # Calculate output length statistics + lengths = [len(str(result.output)) for result in item_results] + results.extend([ + {"name": "avg_output_length", "value": sum(lengths) / len(lengths)}, + {"name": "min_output_length", "value": min(lengths)}, + {"name": "max_output_length", "value": max(lengths)} + ]) + + # Success rate + total_items = len(item_results) # Only successful items are included + results.append({ + "name": "processing_success_rate", + "value": 1.0, # All items in item_results succeeded + "comment": f"Successfully processed {total_items} items" + }) + + return results + ``` + + Async run evaluator with external analysis: + ```python + async def llm_batch_analysis(*, item_results, **kwargs): + # Prepare batch analysis prompt + outputs = [result.output for result in item_results] + prompt = f"Analyze these {len(outputs)} outputs for common themes:\n" + prompt += "\n".join(f"{i+1}. {output}" for i, output in enumerate(outputs)) + + response = await openai_client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": prompt}] + ) + + return { + "name": "thematic_analysis", + "value": response.choices[0].message.content, + "comment": f"LLM analysis of {len(outputs)} outputs" + } + ``` + + Performance distribution analysis: + ```python + def performance_distribution(*, item_results, **kwargs): + # Extract all evaluation scores + all_scores = [] + score_by_metric = {} + + for result in item_results: + for evaluation in result.evaluations: + metric_name = evaluation.name + value = evaluation.value + + if isinstance(value, (int, float)): + all_scores.append(value) + if metric_name not in score_by_metric: + score_by_metric[metric_name] = [] + score_by_metric[metric_name].append(value) + + results = [] + + # Overall score distribution + if all_scores: + import statistics + results.append({ + "name": "score_std_dev", + "value": statistics.stdev(all_scores) if len(all_scores) > 1 else 0, + "comment": f"Standard deviation across all numeric scores" + }) + + # Per-metric statistics + for metric, scores in score_by_metric.items(): + if len(scores) > 1: + results.append({ + "name": f"{metric}_variance", + "value": statistics.variance(scores), + "comment": f"Variance in {metric} across {len(scores)} items" + }) + + return results + ``` + """ + ... + + +def _format_value(value: Any) -> str: + """Format a value for display.""" + if isinstance(value, str): + return value[:50] + "..." if len(value) > 50 else value + return str(value) + + +async def _run_evaluator( + evaluator: Union[EvaluatorFunction, RunEvaluatorFunction], **kwargs: Any +) -> List[Evaluation]: + """Run an evaluator function and normalize the result.""" + try: + result = evaluator(**kwargs) + + # Handle async evaluators + if asyncio.iscoroutine(result): + result = await result + + # Normalize to list + if isinstance(result, (dict, Evaluation)): + return [result] # type: ignore + + elif isinstance(result, list): + return result + + else: + return [] + + except Exception as e: + evaluator_name = getattr(evaluator, "__name__", "unknown_evaluator") + logging.getLogger("langfuse").error(f"Evaluator {evaluator_name} failed: {e}") + return [] + + +async def _run_task(task: TaskFunction, item: ExperimentItem) -> Any: + """Run a task function and handle sync/async.""" + result = task(item=item) + + # Handle async tasks + if asyncio.iscoroutine(result): + result = await result + + return result + + +def create_evaluator_from_autoevals( + autoevals_evaluator: Any, **kwargs: Optional[Dict[str, Any]] +) -> EvaluatorFunction: + """Create a Langfuse evaluator from an autoevals evaluator. + + Args: + autoevals_evaluator: An autoevals evaluator instance + **kwargs: Additional arguments passed to the evaluator + + Returns: + A Langfuse-compatible evaluator function + """ + + def langfuse_evaluator( + *, + input: Any, + output: Any, + expected_output: Any, + metadata: Optional[Dict[str, Any]], + **langfuse_kwargs: Dict[str, Any], + ) -> Evaluation: + evaluation = autoevals_evaluator( + input=input, output=output, expected=expected_output, **kwargs + ) + + return Evaluation( + name=evaluation.name, value=evaluation.score, metadata=evaluation.metadata + ) + + return langfuse_evaluator diff --git a/langfuse/types.py b/langfuse/types.py index b654fffed..32ebb32d4 100644 --- a/langfuse/types.py +++ b/langfuse/types.py @@ -1,4 +1,21 @@ -"""@private""" +"""Public API for all Langfuse types. + +This module provides a centralized location for importing commonly used types +from the Langfuse SDK, making them easily accessible without requiring nested imports. + +Example: + ```python + from langfuse.types import Evaluation, LocalExperimentItem, TaskFunction + + # Define your task function + def my_task(*, item: LocalExperimentItem, **kwargs) -> str: + return f"Processed: {item['input']}" + + # Define your evaluator + def my_evaluator(*, output: str, **kwargs) -> Evaluation: + return {"name": "length", "value": len(output)} + ``` +""" from datetime import datetime from typing import ( @@ -84,3 +101,14 @@ class ParsedMediaReference(TypedDict): class TraceContext(TypedDict): trace_id: str parent_span_id: NotRequired[str] + + +__all__ = [ + "SpanLevel", + "ScoreDataType", + "TraceMetadata", + "ObservationParams", + "MaskFunction", + "ParsedMediaReference", + "TraceContext", +] diff --git a/poetry.lock b/poetry.lock index 41b0b421b..b8cb3eab9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -18,7 +17,6 @@ version = "4.10.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, @@ -39,20 +37,59 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"langchain\" and python_version < \"3.11\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "autoevals" +version = "0.0.130" +description = "Universal library for evaluating AI models" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, + {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, +] + +[package.dependencies] +chevron = "*" +jsonschema = "*" +polyleven = "*" +pyyaml = "*" + +[package.extras] +all = ["IPython", "black (==22.6.0)", "braintrust", "build", "flake8", "flake8-isort", "isort (==5.12.0)", "numpy", "openai", "pre-commit", "pydoc-markdown", "pytest", "respx", "scipy", "twine"] +dev = ["IPython", "black (==22.6.0)", "braintrust", "build", "flake8", "flake8-isort", "isort (==5.12.0)", "openai", "pre-commit", "pytest", "respx", "twine"] +doc = ["pydoc-markdown"] +scipy = ["numpy", "scipy"] + [[package]] name = "backoff" version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -64,8 +101,6 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -77,7 +112,6 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -89,7 +123,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -101,7 +134,6 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -184,18 +216,27 @@ files = [ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] +[[package]] +name = "chevron" +version = "0.14.0" +description = "Mustache templating language renderer" +optional = false +python-versions = "*" +files = [ + {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, + {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, +] + [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "extra == \"openai\" and platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -203,7 +244,6 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -215,12 +255,10 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -markers = {main = "extra == \"openai\""} [[package]] name = "exceptiongroup" @@ -228,8 +266,6 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -247,7 +283,6 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -262,7 +297,6 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -274,7 +308,6 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -292,8 +325,6 @@ version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"langchain\"" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -361,7 +392,6 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -373,7 +403,6 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -395,7 +424,6 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -408,7 +436,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -420,7 +448,6 @@ version = "2.6.13" description = "File identification library for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, @@ -435,7 +462,6 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -450,7 +476,6 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -460,12 +485,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -474,7 +499,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -486,7 +510,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -504,7 +527,6 @@ version = "0.10.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, @@ -584,7 +606,6 @@ files = [ {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] -markers = {main = "extra == \"openai\""} [[package]] name = "jsonpatch" @@ -592,12 +613,10 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -608,12 +627,45 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] -markers = {main = "extra == \"langchain\""} + +[[package]] +name = "jsonschema" +version = "4.25.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, + {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +files = [ + {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, + {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, +] + +[package.dependencies] +referencing = ">=0.31.0" [[package]] name = "langchain" @@ -621,8 +673,6 @@ version = "0.3.27" description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "extra == \"langchain\"" files = [ {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, @@ -663,12 +713,10 @@ version = "0.3.75" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.75-py3-none-any.whl", hash = "sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5"}, {file = "langchain_core-0.3.75.tar.gz", hash = "sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" @@ -685,7 +733,6 @@ version = "0.3.32" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langchain_openai-0.3.32-py3-none-any.whl", hash = "sha256:3354f76822f7cc76d8069831fe2a77f9bc7ff3b4f13af788bd94e4c6e853b400"}, {file = "langchain_openai-0.3.32.tar.gz", hash = "sha256:782ad669bd1bdb964456d8882c5178717adcfceecb482cc20005f770e43d346d"}, @@ -702,8 +749,6 @@ version = "0.3.9" description = "LangChain text splitting utilities" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"langchain\"" files = [ {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, @@ -718,7 +763,6 @@ version = "0.6.7" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph-0.6.7-py3-none-any.whl", hash = "sha256:c724dd8c24806b70faf4903e8e20c0234f8c0a356e0e96a88035cbecca9df2cf"}, {file = "langgraph-0.6.7.tar.gz", hash = "sha256:ba7fd17b8220142d6a4269b6038f2b3dcbcef42cd5ecf4a4c8d9b60b010830a6"}, @@ -738,7 +782,6 @@ version = "2.1.1" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, @@ -754,7 +797,6 @@ version = "0.6.4" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph_prebuilt-0.6.4-py3-none-any.whl", hash = "sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344"}, {file = "langgraph_prebuilt-0.6.4.tar.gz", hash = "sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f"}, @@ -770,7 +812,6 @@ version = "0.2.3" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph_sdk-0.2.3-py3-none-any.whl", hash = "sha256:059edfe2f62708c2e54239e170f5a33f796d456dbdbde64276c16cac8b97ba99"}, {file = "langgraph_sdk-0.2.3.tar.gz", hash = "sha256:17398aeae0f937cae1c8eb9027ada2969abdb50fe8ed3246c78f543b679cf959"}, @@ -786,12 +827,10 @@ version = "0.4.19" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "langsmith-0.4.19-py3-none-any.whl", hash = "sha256:4c50ae47e9f8430a06adb54bceaf32808f5e54fcb8186731bf7b2dab3fc30621"}, {file = "langsmith-0.4.19.tar.gz", hash = "sha256:71916bef574f72c40887ce371a4502d80c80efc2a053df123f1347e79ea83dca"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -815,7 +854,6 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -886,7 +924,6 @@ version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, @@ -947,7 +984,6 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -959,7 +995,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -971,12 +1006,10 @@ version = "1.102.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -1000,7 +1033,6 @@ version = "1.36.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, @@ -1016,7 +1048,6 @@ version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, @@ -1031,7 +1062,6 @@ version = "1.36.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, @@ -1052,7 +1082,6 @@ version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, @@ -1067,7 +1096,6 @@ version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, @@ -1084,7 +1112,6 @@ version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, @@ -1100,7 +1127,6 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1186,7 +1212,6 @@ files = [ {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, ] -markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} [[package]] name = "ormsgpack" @@ -1194,7 +1219,6 @@ version = "1.10.0" description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, @@ -1245,7 +1269,6 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1257,7 +1280,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1269,7 +1291,6 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1286,7 +1307,6 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -1303,7 +1323,6 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1313,13 +1332,76 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "polyleven" +version = "0.9.0" +description = "A fast C-implemented library for Levenshtein distance" +optional = false +python-versions = ">=3.8" +files = [ + {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, + {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, + {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a1d3f1b385e9f51090beca54925a0fd0ab2d744fcea91dd9353c7b13bbb274f"}, + {file = "polyleven-0.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2be92bb7743e3b3e14a2b894902f4ceeea5700849dd9e9ab59c68bd7943b3d85"}, + {file = "polyleven-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bd784bad5164d0d4e823d98aa8ffdc118c14d211dfd7271ede7f1baa7efc691"}, + {file = "polyleven-0.9.0-cp310-cp310-win32.whl", hash = "sha256:bac610f5a30b56ab2fbb1a3de071ef9ed3aa6a572a80a4cfbf0665929e0f6451"}, + {file = "polyleven-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4e4ab3cfc196907751adb3b65959ad8be08fc06679d071fdf01e5225f394812e"}, + {file = "polyleven-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e58bbcd3f062043fa67e76e89f803eb308ea06fbb4dc6f32d7063c37f1c16dfd"}, + {file = "polyleven-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fd803de02e99f51ade3fcae4e5be50c89c1ff360213bcdbcf98820e2633c71a"}, + {file = "polyleven-0.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff60e2da0864b3d4bec2826eadbbb0a8967384d53bec9e693aad7b0089e1258c"}, + {file = "polyleven-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:259856641423ca82230237d637869301ba02971c24283101b67c8117e7116b7a"}, + {file = "polyleven-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a46e7b364b3936f025022d1182e10cba9ac45974dc2cafa17b7f9f515784adb5"}, + {file = "polyleven-0.9.0-cp311-cp311-win32.whl", hash = "sha256:6f0fd999efaa0d5409603ae7e44b60152b8d12a190b54115bcf0ba93e41e09f1"}, + {file = "polyleven-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:65a6e899db184bce6384526e46f446c6c159a2b0bb3b463dcc78a2bc8ddf85f5"}, + {file = "polyleven-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9c905fa0862c1f3e27e948a713fb86a26ce1659f1d90b1b4aff04a8890213b"}, + {file = "polyleven-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7058bea0da4893ebb8bedd9f638ec4e026c150e29b7b7385db5c157742d0ff11"}, + {file = "polyleven-0.9.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99fcfc48c1eaacc4a46dd9d22dc98de111120c66b56df14257f276b762bd591"}, + {file = "polyleven-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:29ef7db85a7bb01be9372461bc8d8993d4817dfcea702e4d2b8f0d9c43415ebe"}, + {file = "polyleven-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:288bfe0a0040421c52a5dc312b55c47812a72fb9cd7e6d19859ac2f9f11f350f"}, + {file = "polyleven-0.9.0-cp312-cp312-win32.whl", hash = "sha256:7260fa32fff7194e06b4221e0a6d2ba2decd4e4dc51f7f8cddbf365649326ee4"}, + {file = "polyleven-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4db8b16aac237dbf644a0e4323c3ba0907dab6adecd2a345bf2fa92301d7fb2d"}, + {file = "polyleven-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45cea2885c61bda9711244a51aed068f9a55f1d776d4caad6c574a3f401945ae"}, + {file = "polyleven-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62b039e9dc8fa53ad740de02d168a7e9d0edce3734b2927f40fe851b328b766f"}, + {file = "polyleven-0.9.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a0c1ecd2dc356fd94edc80e18a30ad28e93ccc840127e765b83ad60426b2d5"}, + {file = "polyleven-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:20576da0c8000bd1c4a07cee43db9169b7d094f5dcc03b20775506d07c56f4fb"}, + {file = "polyleven-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba356ce9e7e7e8ddf4eff17eb39df5b822cb8899450c6d289a22249b78c9a5f4"}, + {file = "polyleven-0.9.0-cp313-cp313-win32.whl", hash = "sha256:244d759986486252121061d727a642d3505cbdd9e6616467b42935e662a9fa61"}, + {file = "polyleven-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f671df664924b3ec14195be7bf778d5f71811989e59a3f9547f8066cefc596f"}, + {file = "polyleven-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7309296f1f91e7aa7d292e5b9aa0da53f2ce7997cfda8535155424a791fe73c8"}, + {file = "polyleven-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c71e238153acdf010c7fe6f18835dd6d7ca37a7e7cca08d51c2234e2227019"}, + {file = "polyleven-0.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecf0a858b7694acea0f7459f8699f8b1f62ee99d88529b01f3a1597aa4c53978"}, + {file = "polyleven-0.9.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c903c9b70a089c5f2b5990ce3a09ac1ce39d0b1ea93ec8c9e1eb217ddea779c6"}, + {file = "polyleven-0.9.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e9608f5835f8fb3778aaad2b126aaea201cd9a6b210286533762c29cd3debcf2"}, + {file = "polyleven-0.9.0-cp38-cp38-win32.whl", hash = "sha256:aabd963fef557f6afe4306920cbd6c580aff572c8a96c5d6bf572fb9c4bdce46"}, + {file = "polyleven-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:e8c4c3c6515f4753fe69becb4686009bc5a5776752fd27a3d34d89f54f8c40e6"}, + {file = "polyleven-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c672c982108a48c7aebd7016aa8482b8ee96f01280a68cbee56293055aebdfc7"}, + {file = "polyleven-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a4f857c9f7fd99b7e41305e6cdb30d39592b1a6ca50fbc20edd175746e376ca"}, + {file = "polyleven-0.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26e06e1da0734c8d5a1625589d2bd213f9d40d0023370475c167dc773239ab78"}, + {file = "polyleven-0.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9859199fefc85329b495cd0ce5b34df1a9acf6623d3dbaff5fcb688ade59fb88"}, + {file = "polyleven-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58703ae7483b46a5e05d2d3f2cac2e345b96b57faaebfe09c5890eb5346daf31"}, + {file = "polyleven-0.9.0-cp39-cp39-win32.whl", hash = "sha256:92a0d2e4d6230f2ccc14d12d11cb496d5d5b81d975841bfed9dce6d11cf90826"}, + {file = "polyleven-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:1d651a6714caf4d144f8cb0bd6b1eb043a2ca80dd7c6d87b8f8020edc1729149"}, + {file = "polyleven-0.9.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0a59f3cf5297e22aac73cf439e1e9cb0703af1adc853fb911637172db09bddec"}, + {file = "polyleven-0.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3c8581d8eae56d0e0e3cce33384b4365ef29a924f48edc6b3b5a694412c4b7d"}, + {file = "polyleven-0.9.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:603f0ea18dc0826f7078c14484c227dcdb61ca8e4485d0b67f2df317a3a01726"}, + {file = "polyleven-0.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8cf8ff07ea44947e9a34ab371a3b0fec4d2328957332185445cfdd1675539cb9"}, + {file = "polyleven-0.9.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf4fb8f5be74b9bf7e6f7c2014ee153dc4208af337b781cf3aafc5f51a647d80"}, + {file = "polyleven-0.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f21e6c050f6f0d259cf9c6367042ba6a69e553b8294143c83bb47f6481486f9c"}, + {file = "polyleven-0.9.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c74d8cba499541fe96e96a76cb8ac2bac7f3d7efeb8c2cec1bf1383c91790f4"}, + {file = "polyleven-0.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5260411e820a858728d32f161690a54bc2162644dba8f4e2b0dd72707d00ac20"}, + {file = "polyleven-0.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:81ae9a154c82d53ff67d6cd6b4ee96de3e449f2c8cccd49aaa62b50f6e57a4eb"}, + {file = "polyleven-0.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef398fe2759f84a6c088320742f09ecef5904e5c1f60668eed08f431221c5239"}, + {file = "polyleven-0.9.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3163f6c7ad192ee14ef760b1dd3143a3107c483a327dcfb5e6c94d4c8217fa4"}, + {file = "polyleven-0.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:87ef064bfe4a1b13414e440f56a716096375ec93cf1351bed9a84942c230c715"}, + {file = "polyleven-0.9.0.tar.gz", hash = "sha256:299a93766761b5e5fb4092388f3dc6401224fd436c05f11c4ee48b262587e8da"}, +] + [[package]] name = "pre-commit" version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1338,7 +1420,6 @@ version = "6.32.0" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, @@ -1357,7 +1438,6 @@ version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, @@ -1371,7 +1451,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -1379,7 +1459,6 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1491,7 +1570,6 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1506,7 +1584,6 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -1530,7 +1607,6 @@ version = "1.1.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, @@ -1551,7 +1627,6 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1566,7 +1641,6 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1581,7 +1655,6 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1602,7 +1675,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1658,7 +1730,22 @@ files = [ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] -markers = {main = "extra == \"langchain\""} + +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" @@ -1666,7 +1753,6 @@ version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, @@ -1763,7 +1849,6 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1785,23 +1870,184 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "rpds-py" +version = "0.27.1" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, + {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1"}, + {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10"}, + {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808"}, + {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8"}, + {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9"}, + {file = "rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4"}, + {file = "rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1"}, + {file = "rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881"}, + {file = "rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a"}, + {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde"}, + {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21"}, + {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9"}, + {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948"}, + {file = "rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39"}, + {file = "rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15"}, + {file = "rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746"}, + {file = "rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90"}, + {file = "rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a"}, + {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444"}, + {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a"}, + {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1"}, + {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998"}, + {file = "rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39"}, + {file = "rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594"}, + {file = "rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502"}, + {file = "rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b"}, + {file = "rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d"}, + {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274"}, + {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd"}, + {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2"}, + {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002"}, + {file = "rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3"}, + {file = "rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83"}, + {file = "rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d"}, + {file = "rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228"}, + {file = "rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21"}, + {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef"}, + {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081"}, + {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd"}, + {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7"}, + {file = "rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688"}, + {file = "rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797"}, + {file = "rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334"}, + {file = "rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9"}, + {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60"}, + {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e"}, + {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212"}, + {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675"}, + {file = "rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3"}, + {file = "rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456"}, + {file = "rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3"}, + {file = "rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2"}, + {file = "rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48"}, + {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb"}, + {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734"}, + {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb"}, + {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0"}, + {file = "rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a"}, + {file = "rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772"}, + {file = "rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527"}, + {file = "rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e"}, + {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e"}, + {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786"}, + {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec"}, + {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b"}, + {file = "rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52"}, + {file = "rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b"}, + {file = "rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6"}, + {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c"}, + {file = "rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859"}, + {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"}, +] + [[package]] name = "ruff" version = "0.12.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065"}, {file = "ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93"}, @@ -1830,7 +2076,6 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1842,8 +2087,6 @@ version = "2.0.43" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"langchain\"" files = [ {file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"}, {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"}, @@ -1939,12 +2182,10 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] -markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx"] @@ -1956,7 +2197,6 @@ version = "0.11.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "tiktoken-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917"}, {file = "tiktoken-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0"}, @@ -2004,8 +2244,6 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2047,12 +2285,10 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -2070,7 +2306,6 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2082,7 +2317,6 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -2097,14 +2331,13 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2115,7 +2348,6 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -2129,7 +2361,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "werkzeug" @@ -2137,7 +2369,6 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2155,7 +2386,6 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2246,7 +2476,6 @@ version = "3.5.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, @@ -2379,14 +2608,13 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2399,7 +2627,6 @@ version = "0.24.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "zstandard-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af1394c2c5febc44e0bbf0fc6428263fa928b50d1b1982ce1d870dc793a8e5f4"}, {file = "zstandard-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e941654cef13a1d53634ec30933722eda11f44f99e1d0bc62bbce3387580d50"}, @@ -2501,16 +2728,15 @@ files = [ {file = "zstandard-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:8ecd3b1f7a601f79e0cd20c26057d770219c0dc2f572ea07390248da2def79a4"}, {file = "zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f"}, ] -markers = {main = "extra == \"langchain\""} [package.extras] -cffi = ["cffi (>=1.17) ; python_version >= \"3.13\" and platform_python_implementation != \"PyPy\""] +cffi = ["cffi (>=1.17)"] [extras] langchain = ["langchain"] openai = ["openai"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "6fe7fed47d629061be2cfcd2a2ea4c83201e5de130faf5f664d68845c2fea22f" +content-hash = "83ae81e7b9fd90ae8000dc0ac491ff766b899b166a5fc895043d0555267e288c" diff --git a/pyproject.toml b/pyproject.toml index c2c6c95d9..369f95ce6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ ruff = ">=0.1.8,<0.13.0" mypy = "^1.0.0" langchain-openai = ">=0.0.5,<0.4" langgraph = ">=0.2.62,<0.7.0" +autoevals = "^0.0.130" [tool.poetry.group.docs.dependencies] pdoc = "^15.0.4" diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 9a758e38a..f29851d84 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1934,8 +1934,8 @@ def test_start_as_current_observation_types(): def test_that_generation_like_properties_are_actually_created(): """Test that generation-like observation types properly support generation properties.""" from langfuse._client.constants import ( - get_observation_types_list, ObservationTypeGenerationLike, + get_observation_types_list, ) langfuse = Langfuse() diff --git a/tests/test_experiments.py b/tests/test_experiments.py new file mode 100644 index 000000000..168310970 --- /dev/null +++ b/tests/test_experiments.py @@ -0,0 +1,670 @@ +"""Comprehensive tests for Langfuse experiment functionality matching JS SDK.""" + +import time +from typing import Any, Dict, List + +import pytest + +from langfuse import get_client +from langfuse.experiment import ( + Evaluation, + ExperimentData, + ExperimentItem, + ExperimentItemResult, +) +from tests.utils import create_uuid, get_api + + +@pytest.fixture +def sample_dataset(): + """Sample dataset for experiments.""" + return [ + {"input": "Germany", "expected_output": "Berlin"}, + {"input": "France", "expected_output": "Paris"}, + {"input": "Spain", "expected_output": "Madrid"}, + ] + + +def mock_task(*, item: ExperimentItem, **kwargs: Dict[str, Any]): + """Mock task function that simulates processing.""" + input_val = ( + item.get("input") + if isinstance(item, dict) + else getattr(item, "input", "unknown") + ) + return f"Capital of {input_val}" + + +def simple_evaluator(*, input, output, expected_output=None, **kwargs): + """Return output length.""" + return Evaluation(name="length_check", value=len(output)) + + +def factuality_evaluator(*, input, output, expected_output=None, **kwargs): + """Mock factuality evaluator.""" + # Simple mock: check if expected output is in the output + if expected_output and expected_output.lower() in output.lower(): + return Evaluation(name="factuality", value=1.0, comment="Correct answer found") + return Evaluation(name="factuality", value=0.0, comment="Incorrect answer") + + +def run_evaluator_average_length(*, item_results: List[ExperimentItemResult], **kwargs): + """Run evaluator that calculates average output length.""" + if not item_results: + return Evaluation(name="average_length", value=0) + + avg_length = sum(len(r.output) for r in item_results) / len(item_results) + + return Evaluation(name="average_length", value=avg_length) + + +# Basic Functionality Tests +def test_run_experiment_on_local_dataset(sample_dataset): + """Test running experiment on local dataset.""" + langfuse_client = get_client() + + result = langfuse_client.run_experiment( + name="Euro capitals", + description="Country capital experiment", + data=sample_dataset, + task=mock_task, + evaluators=[simple_evaluator, factuality_evaluator], + run_evaluators=[run_evaluator_average_length], + ) + + # Validate basic result structure + assert len(result.item_results) == 3 + assert len(result.run_evaluations) == 1 + assert result.run_evaluations[0].name == "average_length" + assert result.dataset_run_id is None # No dataset_run_id for local datasets + + # Validate item results structure + for item_result in result.item_results: + assert hasattr(item_result, "output") + assert hasattr(item_result, "evaluations") + assert hasattr(item_result, "trace_id") + assert ( + item_result.dataset_run_id is None + ) # No dataset_run_id for local datasets + assert len(item_result.evaluations) == 2 # Both evaluators should run + + # Flush and wait for server processing + langfuse_client.flush() + time.sleep(2) + + # Validate traces are correctly persisted with input/output/metadata + api = get_api() + expected_inputs = ["Germany", "France", "Spain"] + expected_outputs = ["Capital of Germany", "Capital of France", "Capital of Spain"] + + for i, item_result in enumerate(result.item_results): + trace_id = item_result.trace_id + assert trace_id is not None, f"Item {i} should have a trace_id" + + # Fetch trace from API + trace = api.trace.get(trace_id) + assert trace is not None, f"Trace {trace_id} should exist" + + # Validate trace name + assert ( + trace.name == "experiment-item-run" + ), f"Trace {trace_id} should have correct name" + + # Validate trace input - should contain the experiment item + assert trace.input is not None, f"Trace {trace_id} should have input" + expected_input = expected_inputs[i] + # The input should contain the item data in some form + assert expected_input in str( + trace.input + ), f"Trace {trace_id} input should contain '{expected_input}'" + + # Validate trace output - should be the task result + assert trace.output is not None, f"Trace {trace_id} should have output" + expected_output = expected_outputs[i] + assert ( + trace.output == expected_output + ), f"Trace {trace_id} output should be '{expected_output}', got '{trace.output}'" + + # Validate trace metadata contains experiment name + assert trace.metadata is not None, f"Trace {trace_id} should have metadata" + assert ( + "experiment_name" in trace.metadata + ), f"Trace {trace_id} metadata should contain experiment_name" + assert ( + trace.metadata["experiment_name"] == "Euro capitals" + ), f"Trace {trace_id} metadata should have correct experiment_name" + + +def test_run_experiment_on_langfuse_dataset(): + """Test running experiment on Langfuse dataset.""" + langfuse_client = get_client() + # Create dataset + dataset_name = "test-dataset-" + create_uuid() + langfuse_client.create_dataset(name=dataset_name) + + # Add items to dataset + test_items = [ + {"input": "Germany", "expected_output": "Berlin"}, + {"input": "France", "expected_output": "Paris"}, + ] + + for item in test_items: + langfuse_client.create_dataset_item( + dataset_name=dataset_name, + input=item["input"], + expected_output=item["expected_output"], + ) + + # Get dataset and run experiment + dataset = langfuse_client.get_dataset(dataset_name) + + # Use unique experiment name for proper identification + experiment_name = "Dataset Test " + create_uuid()[:8] + result = dataset.run_experiment( + name=experiment_name, + description="Test on Langfuse dataset", + task=mock_task, + evaluators=[factuality_evaluator], + run_evaluators=[run_evaluator_average_length], + ) + + # Should have dataset run ID for Langfuse datasets + assert result.dataset_run_id is not None + assert len(result.item_results) == 2 + assert all(item.dataset_run_id is not None for item in result.item_results) + + # Flush and wait for server processing + langfuse_client.flush() + time.sleep(3) + + # Verify dataset run exists via API + api = get_api() + dataset_run = api.datasets.get_run( + dataset_name=dataset_name, run_name=result.run_name + ) + + # Validate traces are correctly persisted with input/output/metadata + expected_data = {"Germany": "Capital of Germany", "France": "Capital of France"} + dataset_run_id = result.dataset_run_id + + # Create a mapping from dataset item ID to dataset item for validation + dataset_item_map = {item.id: item for item in dataset.items} + + for i, item_result in enumerate(result.item_results): + trace_id = item_result.trace_id + assert trace_id is not None, f"Item {i} should have a trace_id" + + # Fetch trace from API + trace = api.trace.get(trace_id) + assert trace is not None, f"Trace {trace_id} should exist" + + # Validate trace name + assert ( + trace.name == "experiment-item-run" + ), f"Trace {trace_id} should have correct name" + + # Validate trace input and output match expected pairs + assert trace.input is not None, f"Trace {trace_id} should have input" + trace_input_str = str(trace.input) + + # Find which expected input this trace corresponds to + matching_input = None + for expected_input in expected_data.keys(): + if expected_input in trace_input_str: + matching_input = expected_input + break + + assert ( + matching_input is not None + ), f"Trace {trace_id} input '{trace_input_str}' should contain one of {list(expected_data.keys())}" + + # Validate trace output matches the expected output for this input + assert trace.output is not None, f"Trace {trace_id} should have output" + expected_output = expected_data[matching_input] + assert ( + trace.output == expected_output + ), f"Trace {trace_id} output should be '{expected_output}', got '{trace.output}'" + + # Validate trace metadata contains experiment and dataset info + assert trace.metadata is not None, f"Trace {trace_id} should have metadata" + assert ( + "experiment_name" in trace.metadata + ), f"Trace {trace_id} metadata should contain experiment_name" + assert ( + trace.metadata["experiment_name"] == experiment_name + ), f"Trace {trace_id} metadata should have correct experiment_name" + + # Validate dataset-specific metadata fields + assert ( + "dataset_id" in trace.metadata + ), f"Trace {trace_id} metadata should contain dataset_id" + assert ( + trace.metadata["dataset_id"] == dataset.id + ), f"Trace {trace_id} metadata should have correct dataset_id" + + assert ( + "dataset_item_id" in trace.metadata + ), f"Trace {trace_id} metadata should contain dataset_item_id" + # Get the dataset item ID from metadata and validate it exists + dataset_item_id = trace.metadata["dataset_item_id"] + assert ( + dataset_item_id in dataset_item_map + ), f"Trace {trace_id} metadata dataset_item_id should correspond to a valid dataset item" + + # Validate the dataset item input matches the trace input + dataset_item = dataset_item_map[dataset_item_id] + assert ( + dataset_item.input == matching_input + ), f"Trace {trace_id} should correspond to dataset item with input '{matching_input}'" + + assert dataset_run is not None, f"Dataset run {dataset_run_id} should exist" + assert dataset_run.name == result.run_name, "Dataset run should have correct name" + assert ( + dataset_run.description == "Test on Langfuse dataset" + ), "Dataset run should have correct description" + + # Get dataset run items to verify trace linkage + dataset_run_items = api.dataset_run_items.list( + dataset_id=dataset.id, run_name=result.run_name + ) + assert len(dataset_run_items.data) == 2, "Dataset run should have 2 items" + + # Verify each dataset run item links to the correct trace + run_item_trace_ids = { + item.trace_id for item in dataset_run_items.data if item.trace_id + } + result_trace_ids = {item.trace_id for item in result.item_results} + + assert run_item_trace_ids == result_trace_ids, ( + f"Dataset run items should link to the same traces as experiment results. " + f"Run items: {run_item_trace_ids}, Results: {result_trace_ids}" + ) + + +# Error Handling Tests +def test_evaluator_failures_handled_gracefully(): + """Test that evaluator failures don't break the experiment.""" + langfuse_client = get_client() + + def failing_evaluator(**kwargs): + raise Exception("Evaluator failed") + + def working_evaluator(**kwargs): + return Evaluation(name="working_eval", value=1.0) + + result = langfuse_client.run_experiment( + name="Error test", + data=[{"input": "test"}], + task=lambda **kwargs: "result", + evaluators=[working_evaluator, failing_evaluator], + ) + + # Should complete with only working evaluator + assert len(result.item_results) == 1 + # Only the working evaluator should have produced results + assert ( + len( + [ + eval + for eval in result.item_results[0].evaluations + if eval.name == "working_eval" + ] + ) + == 1 + ) + + langfuse_client.flush() + time.sleep(1) + + +def test_task_failures_handled_gracefully(): + """Test that task failures are handled gracefully and don't stop the experiment.""" + langfuse_client = get_client() + + def failing_task(item): + raise Exception("Task failed") + + def working_task(item): + return f"Processed: {item['input']}" + + # Test with mixed data - some will fail, some will succeed + result = langfuse_client.run_experiment( + name="Task error test", + data=[{"input": "test1"}, {"input": "test2"}], + task=failing_task, + ) + + # Should complete but with no valid results since all tasks failed + assert len(result.item_results) == 0 + + langfuse_client.flush() + time.sleep(1) + + +def test_run_evaluator_failures_handled(): + """Test that run evaluator failures don't break the experiment.""" + langfuse_client = get_client() + + def failing_run_evaluator(**kwargs): + raise Exception("Run evaluator failed") + + result = langfuse_client.run_experiment( + name="Run evaluator error test", + data=[{"input": "test"}], + task=lambda **kwargs: "result", + run_evaluators=[failing_run_evaluator], + ) + + # Should complete but run evaluations should be empty + assert len(result.item_results) == 1 + assert len(result.run_evaluations) == 0 + + langfuse_client.flush() + time.sleep(1) + + +# Edge Cases Tests +def test_empty_dataset_handling(): + """Test experiment with empty dataset.""" + langfuse_client = get_client() + + result = langfuse_client.run_experiment( + name="Empty dataset test", + data=[], + task=lambda **kwargs: "result", + run_evaluators=[run_evaluator_average_length], + ) + + assert len(result.item_results) == 0 + assert len(result.run_evaluations) == 1 # Run evaluators still execute + + langfuse_client.flush() + time.sleep(1) + + +def test_dataset_with_missing_fields(): + """Test handling dataset with missing fields.""" + langfuse_client = get_client() + + incomplete_dataset = [ + {"input": "Germany"}, # Missing expected_output + {"expected_output": "Paris"}, # Missing input + {"input": "Spain", "expected_output": "Madrid"}, # Complete + ] + + result = langfuse_client.run_experiment( + name="Incomplete data test", + data=incomplete_dataset, + task=lambda **kwargs: "result", + ) + + # Should handle missing fields gracefully + assert len(result.item_results) == 3 + for item_result in result.item_results: + assert hasattr(item_result, "trace_id") + assert hasattr(item_result, "output") + + langfuse_client.flush() + time.sleep(1) + + +def test_large_dataset_with_concurrency(): + """Test handling large dataset with concurrency control.""" + langfuse_client = get_client() + + large_dataset: ExperimentData = [ + {"input": f"Item {i}", "expected_output": f"Output {i}"} for i in range(20) + ] + + result = langfuse_client.run_experiment( + name="Large dataset test", + data=large_dataset, + task=lambda **kwargs: f"Processed {kwargs['item']}", + evaluators=[lambda **kwargs: Evaluation(name="simple_eval", value=1.0)], + max_concurrency=5, + ) + + assert len(result.item_results) == 20 + for item_result in result.item_results: + assert len(item_result.evaluations) == 1 + assert hasattr(item_result, "trace_id") + + langfuse_client.flush() + time.sleep(3) + + +# Evaluator Configuration Tests +def test_single_evaluation_return(): + """Test evaluators returning single evaluation instead of array.""" + langfuse_client = get_client() + + def single_evaluator(**kwargs): + return Evaluation(name="single_eval", value=1, comment="Single evaluation") + + result = langfuse_client.run_experiment( + name="Single evaluation test", + data=[{"input": "test"}], + task=lambda **kwargs: "result", + evaluators=[single_evaluator], + ) + + assert len(result.item_results) == 1 + assert len(result.item_results[0].evaluations) == 1 + assert result.item_results[0].evaluations[0].name == "single_eval" + + langfuse_client.flush() + time.sleep(1) + + +def test_no_evaluators(): + """Test experiment with no evaluators.""" + langfuse_client = get_client() + + result = langfuse_client.run_experiment( + name="No evaluators test", + data=[{"input": "test"}], + task=lambda **kwargs: "result", + ) + + assert len(result.item_results) == 1 + assert len(result.item_results[0].evaluations) == 0 + assert len(result.run_evaluations) == 0 + + langfuse_client.flush() + time.sleep(1) + + +def test_only_run_evaluators(): + """Test experiment with only run evaluators.""" + langfuse_client = get_client() + + def run_only_evaluator(**kwargs): + return Evaluation( + name="run_only_eval", value=10, comment="Run-level evaluation" + ) + + result = langfuse_client.run_experiment( + name="Only run evaluators test", + data=[{"input": "test"}], + task=lambda **kwargs: "result", + run_evaluators=[run_only_evaluator], + ) + + assert len(result.item_results) == 1 + assert len(result.item_results[0].evaluations) == 0 # No item evaluations + assert len(result.run_evaluations) == 1 + assert result.run_evaluations[0].name == "run_only_eval" + + langfuse_client.flush() + time.sleep(1) + + +def test_different_data_types(): + """Test evaluators returning different data types.""" + langfuse_client = get_client() + + def number_evaluator(**kwargs): + return Evaluation(name="number_eval", value=42) + + def string_evaluator(**kwargs): + return Evaluation(name="string_eval", value="excellent") + + def boolean_evaluator(**kwargs): + return Evaluation(name="boolean_eval", value=True) + + result = langfuse_client.run_experiment( + name="Different data types test", + data=[{"input": "test"}], + task=lambda **kwargs: "result", + evaluators=[number_evaluator, string_evaluator, boolean_evaluator], + ) + + evaluations = result.item_results[0].evaluations + assert len(evaluations) == 3 + + eval_by_name = {e.name: e.value for e in evaluations} + assert eval_by_name["number_eval"] == 42 + assert eval_by_name["string_eval"] == "excellent" + assert eval_by_name["boolean_eval"] is True + + langfuse_client.flush() + time.sleep(1) + + +# Data Persistence Tests +def test_scores_are_persisted(): + """Test that scores are properly persisted to the database.""" + langfuse_client = get_client() + + # Create dataset + dataset_name = "score-persistence-" + create_uuid() + langfuse_client.create_dataset(name=dataset_name) + + langfuse_client.create_dataset_item( + dataset_name=dataset_name, + input="Test input", + expected_output="Test output", + ) + + dataset = langfuse_client.get_dataset(dataset_name) + + def test_evaluator(**kwargs): + return Evaluation( + name="persistence_test", + value=0.85, + comment="Test evaluation for persistence", + ) + + def test_run_evaluator(**kwargs): + return Evaluation( + name="persistence_run_test", + value=0.9, + comment="Test run evaluation for persistence", + ) + + result = dataset.run_experiment( + name="Score persistence test", + run_name="Score persistence test", + description="Test score persistence", + task=mock_task, + evaluators=[test_evaluator], + run_evaluators=[test_run_evaluator], + ) + + assert result.dataset_run_id is not None + assert len(result.item_results) == 1 + assert len(result.run_evaluations) == 1 + + langfuse_client.flush() + time.sleep(3) + + # Verify scores are persisted via API + api = get_api() + dataset_run = api.datasets.get_run( + dataset_name=dataset_name, run_name=result.run_name + ) + + assert dataset_run.name == "Score persistence test" + + +def test_multiple_experiments_on_same_dataset(): + """Test running multiple experiments on the same dataset.""" + langfuse_client = get_client() + + # Create dataset + dataset_name = "multi-experiment-" + create_uuid() + langfuse_client.create_dataset(name=dataset_name) + + for item in [ + {"input": "Germany", "expected_output": "Berlin"}, + {"input": "France", "expected_output": "Paris"}, + ]: + langfuse_client.create_dataset_item( + dataset_name=dataset_name, + input=item["input"], + expected_output=item["expected_output"], + ) + + dataset = langfuse_client.get_dataset(dataset_name) + + # Run first experiment + result1 = dataset.run_experiment( + name="Experiment 1", + run_name="Experiment 1", + description="First experiment", + task=mock_task, + evaluators=[factuality_evaluator], + ) + + langfuse_client.flush() + time.sleep(2) + + # Run second experiment + result2 = dataset.run_experiment( + name="Experiment 2", + run_name="Experiment 2", + description="Second experiment", + task=mock_task, + evaluators=[simple_evaluator], + ) + + langfuse_client.flush() + time.sleep(2) + + # Both experiments should have different run IDs + assert result1.dataset_run_id is not None + assert result2.dataset_run_id is not None + assert result1.dataset_run_id != result2.dataset_run_id + + # Verify both runs exist in database + api = get_api() + runs = api.datasets.get_runs(dataset_name) + assert len(runs.data) >= 2 + + run_names = [run.name for run in runs.data] + assert "Experiment 1" in run_names + assert "Experiment 2" in run_names + + +# Result Formatting Tests +def test_format_experiment_results_basic(): + """Test basic result formatting functionality.""" + langfuse_client = get_client() + + result = langfuse_client.run_experiment( + name="Formatting test", + description="Test result formatting", + data=[{"input": "Hello", "expected_output": "Hi"}], + task=lambda **kwargs: f"Processed: {kwargs['item']}", + evaluators=[simple_evaluator], + run_evaluators=[run_evaluator_average_length], + ) + + # Basic validation that result structure is correct for formatting + assert len(result.item_results) == 1 + assert len(result.run_evaluations) == 1 + assert hasattr(result.item_results[0], "trace_id") + assert hasattr(result.item_results[0], "evaluations") + + langfuse_client.flush() + time.sleep(1) diff --git a/tests/test_openai.py b/tests/test_openai.py index 623802e55..056e4597d 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -94,7 +94,7 @@ def test_openai_chat_completion_stream(openai): assert len(chat_content) > 0 langfuse.flush() - sleep(1) + sleep(3) generation = get_api().observations.get_many( name=generation_name, type="GENERATION" diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 000000000..ac3ee8473 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,254 @@ +"""Test suite for utility functions in langfuse._client.utils module.""" + +import asyncio +import threading +from unittest import mock + +import pytest + +from langfuse._client.utils import run_async_safely + + +class TestRunAsyncSafely: + """Test suite for the run_async_safely function.""" + + def test_run_sync_context_simple(self): + """Test run_async_safely in sync context with simple coroutine.""" + + async def simple_coro(): + await asyncio.sleep(0.01) + return "hello" + + result = run_async_safely(simple_coro()) + assert result == "hello" + + def test_run_sync_context_with_value(self): + """Test run_async_safely in sync context with parameter passing.""" + + async def coro_with_params(value, multiplier=2): + await asyncio.sleep(0.01) + return value * multiplier + + result = run_async_safely(coro_with_params(5, multiplier=3)) + assert result == 15 + + def test_run_sync_context_with_exception(self): + """Test run_async_safely properly propagates exceptions in sync context.""" + + async def failing_coro(): + await asyncio.sleep(0.01) + raise ValueError("Test error") + + with pytest.raises(ValueError, match="Test error"): + run_async_safely(failing_coro()) + + @pytest.mark.asyncio + async def test_run_async_context_simple(self): + """Test run_async_safely from within async context (uses threading).""" + + async def simple_coro(): + await asyncio.sleep(0.01) + return "from_thread" + + # This should use threading since we're already in an async context + result = run_async_safely(simple_coro()) + assert result == "from_thread" + + @pytest.mark.asyncio + async def test_run_async_context_with_exception(self): + """Test run_async_safely properly propagates exceptions from thread.""" + + async def failing_coro(): + await asyncio.sleep(0.01) + raise RuntimeError("Thread error") + + with pytest.raises(RuntimeError, match="Thread error"): + run_async_safely(failing_coro()) + + @pytest.mark.asyncio + async def test_run_async_context_thread_isolation(self): + """Test that threaded execution is properly isolated.""" + # Set a thread-local value in the main async context + threading.current_thread().test_value = "main_thread" + + async def check_thread_isolation(): + # This should run in a different thread + current_thread = threading.current_thread() + # Should not have the test_value from main thread + assert not hasattr(current_thread, "test_value") + return "isolated" + + result = run_async_safely(check_thread_isolation()) + assert result == "isolated" + + def test_multiple_calls_sync_context(self): + """Test multiple sequential calls in sync context.""" + + async def counter_coro(count): + await asyncio.sleep(0.001) + return count * 2 + + results = [] + for i in range(5): + result = run_async_safely(counter_coro(i)) + results.append(result) + + assert results == [0, 2, 4, 6, 8] + + @pytest.mark.asyncio + async def test_multiple_calls_async_context(self): + """Test multiple sequential calls in async context (each uses threading).""" + + async def counter_coro(count): + await asyncio.sleep(0.001) + return count * 3 + + results = [] + for i in range(3): + result = run_async_safely(counter_coro(i)) + results.append(result) + + assert results == [0, 3, 6] + + def test_concurrent_calls_sync_context(self): + """Test concurrent calls in sync context using threading.""" + + async def slow_coro(value): + await asyncio.sleep(0.02) + return value**2 + + import concurrent.futures + + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + futures = [] + for i in range(3): + future = executor.submit(run_async_safely, slow_coro(i + 1)) + futures.append(future) + + results = [future.result() for future in futures] + + # Results should be squares: 1^2, 2^2, 3^2 + assert sorted(results) == [1, 4, 9] + + def test_event_loop_detection_mock(self): + """Test event loop detection logic with mocking.""" + + async def simple_coro(): + return "mocked" + + # Mock no running loop - should use asyncio.run + with mock.patch( + "asyncio.get_running_loop", side_effect=RuntimeError("No loop") + ): + with mock.patch( + "asyncio.run", return_value="asyncio_run_called" + ) as mock_run: + result = run_async_safely(simple_coro()) + assert result == "asyncio_run_called" + mock_run.assert_called_once() + + def test_complex_coroutine(self): + """Test with a more complex coroutine that does actual async work.""" + + async def complex_coro(): + # Simulate some async operations + results = [] + for i in range(3): + await asyncio.sleep(0.001) + results.append(i**2) + + # Simulate concurrent operations + async def sub_task(x): + await asyncio.sleep(0.001) + return x * 10 + + tasks = [sub_task(x) for x in range(2)] + concurrent_results = await asyncio.gather(*tasks) + results.extend(concurrent_results) + + return results + + result = run_async_safely(complex_coro()) + assert result == [0, 1, 4, 0, 10] # [0^2, 1^2, 2^2, 0*10, 1*10] + + @pytest.mark.asyncio + async def test_nested_async_calls(self): + """Test that nested calls to run_async_safely work correctly.""" + + async def inner_coro(value): + await asyncio.sleep(0.001) + return value * 2 + + async def outer_coro(value): + # This is already in an async context, so the inner call + # will also use threading + inner_result = run_async_safely(inner_coro(value)) + await asyncio.sleep(0.001) + return inner_result + 1 + + result = run_async_safely(outer_coro(5)) + assert result == 11 # (5 * 2) + 1 + + def test_exception_types_preserved(self): + """Test that different exception types are properly preserved.""" + + async def custom_exception_coro(): + await asyncio.sleep(0.001) + + class CustomError(Exception): + pass + + raise CustomError("Custom error message") + + with pytest.raises(Exception) as exc_info: + run_async_safely(custom_exception_coro()) + + # The exception type should be preserved + assert "Custom error message" in str(exc_info.value) + + def test_return_types_preserved(self): + """Test that various return types are properly preserved.""" + + async def dict_coro(): + await asyncio.sleep(0.001) + return {"key": "value", "number": 42} + + async def list_coro(): + await asyncio.sleep(0.001) + return [1, 2, 3, "string"] + + async def none_coro(): + await asyncio.sleep(0.001) + return None + + dict_result = run_async_safely(dict_coro()) + assert dict_result == {"key": "value", "number": 42} + assert isinstance(dict_result, dict) + + list_result = run_async_safely(list_coro()) + assert list_result == [1, 2, 3, "string"] + assert isinstance(list_result, list) + + none_result = run_async_safely(none_coro()) + assert none_result is None + + @pytest.mark.asyncio + async def test_real_world_scenario_jupyter_simulation(self): + """Test scenario simulating Jupyter notebook environment.""" + # This simulates being called from a Jupyter cell where there's + # already an event loop running + + async def simulate_llm_call(prompt): + """Simulate an LLM API call.""" + await asyncio.sleep(0.01) # Simulate network delay + return f"Response to: {prompt}" + + async def simulate_experiment_task(item): + """Simulate an experiment task function.""" + response = await simulate_llm_call(item["input"]) + await asyncio.sleep(0.001) # Additional processing + return response + + # This should work even though we're in an async context + result = run_async_safely(simulate_experiment_task({"input": "test prompt"})) + assert result == "Response to: test prompt" From 487a424886c2f172c91cc5b7f76a5aa38db722b4 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:46:57 +0200 Subject: [PATCH 054/296] chore: release v3.4.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 642004f62..28deeaff8 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.3.5" +__version__ = "3.4.0" diff --git a/pyproject.toml b/pyproject.toml index 369f95ce6..ff5ebafac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.3.5" +version = "3.4.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 621b9898ada46a9615afb9db0550830f1ec1525c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:54:50 +0000 Subject: [PATCH 055/296] chore(deps): bump pydantic from 2.11.7 to 2.11.9 (#1343) Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.11.7 to 2.11.9. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v2.11.9/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.11.7...v2.11.9) --- updated-dependencies: - dependency-name: pydantic dependency-version: 2.11.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 150 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 132 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index b8cb3eab9..d88f11dbb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "4.10.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, @@ -37,6 +39,8 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"langchain\" and python_version < \"3.11\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -48,18 +52,19 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "autoevals" @@ -67,6 +72,7 @@ version = "0.0.130" description = "Universal library for evaluating AI models" optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, @@ -90,6 +96,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -101,6 +108,8 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -112,6 +121,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -123,6 +133,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -134,6 +145,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -222,6 +234,7 @@ version = "0.14.0" description = "Mustache templating language renderer" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, @@ -233,10 +246,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "extra == \"openai\" and platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -244,6 +259,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -255,10 +271,12 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "exceptiongroup" @@ -266,6 +284,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -283,6 +303,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -297,6 +318,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -308,6 +330,7 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -325,6 +348,8 @@ version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"langchain\"" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -392,6 +417,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -403,6 +429,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -424,6 +451,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -436,7 +464,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -448,6 +476,7 @@ version = "2.6.13" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, @@ -462,6 +491,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -476,6 +506,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -485,12 +516,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -499,6 +530,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -510,6 +542,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -527,6 +560,7 @@ version = "0.10.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, @@ -606,6 +640,7 @@ files = [ {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] +markers = {main = "extra == \"openai\""} [[package]] name = "jsonpatch" @@ -613,10 +648,12 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -627,10 +664,12 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "jsonschema" @@ -638,6 +677,7 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -659,6 +699,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -673,6 +714,8 @@ version = "0.3.27" description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.9" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, @@ -713,10 +756,12 @@ version = "0.3.75" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.75-py3-none-any.whl", hash = "sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5"}, {file = "langchain_core-0.3.75.tar.gz", hash = "sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpatch = ">=1.33,<2.0" @@ -733,6 +778,7 @@ version = "0.3.32" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langchain_openai-0.3.32-py3-none-any.whl", hash = "sha256:3354f76822f7cc76d8069831fe2a77f9bc7ff3b4f13af788bd94e4c6e853b400"}, {file = "langchain_openai-0.3.32.tar.gz", hash = "sha256:782ad669bd1bdb964456d8882c5178717adcfceecb482cc20005f770e43d346d"}, @@ -749,6 +795,8 @@ version = "0.3.9" description = "LangChain text splitting utilities" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, @@ -763,6 +811,7 @@ version = "0.6.7" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph-0.6.7-py3-none-any.whl", hash = "sha256:c724dd8c24806b70faf4903e8e20c0234f8c0a356e0e96a88035cbecca9df2cf"}, {file = "langgraph-0.6.7.tar.gz", hash = "sha256:ba7fd17b8220142d6a4269b6038f2b3dcbcef42cd5ecf4a4c8d9b60b010830a6"}, @@ -782,6 +831,7 @@ version = "2.1.1" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, @@ -797,6 +847,7 @@ version = "0.6.4" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_prebuilt-0.6.4-py3-none-any.whl", hash = "sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344"}, {file = "langgraph_prebuilt-0.6.4.tar.gz", hash = "sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f"}, @@ -812,6 +863,7 @@ version = "0.2.3" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_sdk-0.2.3-py3-none-any.whl", hash = "sha256:059edfe2f62708c2e54239e170f5a33f796d456dbdbde64276c16cac8b97ba99"}, {file = "langgraph_sdk-0.2.3.tar.gz", hash = "sha256:17398aeae0f937cae1c8eb9027ada2969abdb50fe8ed3246c78f543b679cf959"}, @@ -827,10 +879,12 @@ version = "0.4.19" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "langsmith-0.4.19-py3-none-any.whl", hash = "sha256:4c50ae47e9f8430a06adb54bceaf32808f5e54fcb8186731bf7b2dab3fc30621"}, {file = "langsmith-0.4.19.tar.gz", hash = "sha256:71916bef574f72c40887ce371a4502d80c80efc2a053df123f1347e79ea83dca"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -854,6 +908,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -924,6 +979,7 @@ version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, @@ -984,6 +1040,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -995,6 +1052,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1006,10 +1064,12 @@ version = "1.102.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" @@ -1033,6 +1093,7 @@ version = "1.36.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, @@ -1048,6 +1109,7 @@ version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, @@ -1062,6 +1124,7 @@ version = "1.36.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, @@ -1082,6 +1145,7 @@ version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, @@ -1096,6 +1160,7 @@ version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, @@ -1112,6 +1177,7 @@ version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, @@ -1127,6 +1193,7 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1212,6 +1279,7 @@ files = [ {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, ] +markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} [[package]] name = "ormsgpack" @@ -1219,6 +1287,7 @@ version = "1.10.0" description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, @@ -1269,6 +1338,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1280,6 +1350,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1291,6 +1362,7 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1307,6 +1379,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -1323,6 +1396,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1338,6 +1412,7 @@ version = "0.9.0" description = "A fast C-implemented library for Levenshtein distance" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, @@ -1402,6 +1477,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1420,6 +1496,7 @@ version = "6.32.0" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, @@ -1434,13 +1511,14 @@ files = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, - {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, + {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, + {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, ] [package.dependencies] @@ -1451,7 +1529,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1459,6 +1537,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1570,6 +1649,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1584,6 +1664,7 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -1607,6 +1688,7 @@ version = "1.1.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, @@ -1627,6 +1709,7 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1641,6 +1724,7 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1655,6 +1739,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1675,6 +1760,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1730,6 +1816,7 @@ files = [ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +markers = {main = "extra == \"langchain\""} [[package]] name = "referencing" @@ -1737,6 +1824,7 @@ version = "0.36.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -1753,6 +1841,7 @@ version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, @@ -1849,6 +1938,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1870,10 +1960,12 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] +markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" @@ -1884,6 +1976,7 @@ version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, @@ -2048,6 +2141,7 @@ version = "0.12.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065"}, {file = "ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93"}, @@ -2076,6 +2170,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2087,6 +2182,8 @@ version = "2.0.43" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"langchain\"" files = [ {file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"}, {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"}, @@ -2182,10 +2279,12 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx"] @@ -2197,6 +2296,7 @@ version = "0.11.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tiktoken-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917"}, {file = "tiktoken-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0"}, @@ -2244,6 +2344,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2285,10 +2387,12 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] +markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -2306,6 +2410,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2317,6 +2422,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -2331,13 +2437,14 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2348,6 +2455,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -2361,7 +2469,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "werkzeug" @@ -2369,6 +2477,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2386,6 +2495,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2476,6 +2586,7 @@ version = "3.5.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, @@ -2608,13 +2719,14 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2627,6 +2739,7 @@ version = "0.24.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "zstandard-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af1394c2c5febc44e0bbf0fc6428263fa928b50d1b1982ce1d870dc793a8e5f4"}, {file = "zstandard-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e941654cef13a1d53634ec30933722eda11f44f99e1d0bc62bbce3387580d50"}, @@ -2728,15 +2841,16 @@ files = [ {file = "zstandard-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:8ecd3b1f7a601f79e0cd20c26057d770219c0dc2f572ea07390248da2def79a4"}, {file = "zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f"}, ] +markers = {main = "extra == \"langchain\""} [package.extras] -cffi = ["cffi (>=1.17)"] +cffi = ["cffi (>=1.17) ; python_version >= \"3.13\" and platform_python_implementation != \"PyPy\""] [extras] langchain = ["langchain"] openai = ["openai"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<4.0" content-hash = "83ae81e7b9fd90ae8000dc0ac491ff766b899b166a5fc895043d0555267e288c" From ce291434ee4b7f44fac2d38e8209414b7af9c957 Mon Sep 17 00:00:00 2001 From: Nimar Date: Wed, 17 Sep 2025 16:07:15 +0200 Subject: [PATCH 056/296] fix(prompt-placeholders): don't strip tool calls from placeholder messages (#1346) * add test that tool calls aren't stripped from msg placehodlers * fix: allow arbitrary values to be inserted as placeholder fill-in * fix formatting --- langfuse/model.py | 44 ++++++++++------- tests/test_prompt_compilation.py | 82 ++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/langfuse/model.py b/langfuse/model.py index d1b5a80cf..75803d215 100644 --- a/langfuse/model.py +++ b/langfuse/model.py @@ -165,7 +165,13 @@ def compile( self, **kwargs: Union[str, Any] ) -> Union[ str, - Sequence[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]], + Sequence[ + Union[ + Dict[str, Any], + ChatMessageDict, + ChatMessageWithPlaceholdersDict_Placeholder, + ] + ], ]: pass @@ -327,7 +333,11 @@ def __init__(self, prompt: Prompt_Chat, is_fallback: bool = False): def compile( self, **kwargs: Union[str, Any], - ) -> Sequence[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]]: + ) -> Sequence[ + Union[ + Dict[str, Any], ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder + ] + ]: """Compile the prompt with placeholders and variables. Args: @@ -338,7 +348,11 @@ def compile( List of compiled chat messages as plain dictionaries, with unresolved placeholders kept as-is. """ compiled_messages: List[ - Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder] + Union[ + Dict[str, Any], + ChatMessageDict, + ChatMessageWithPlaceholdersDict_Placeholder, + ] ] = [] unresolved_placeholders: List[ChatMessageWithPlaceholdersDict_Placeholder] = [] @@ -361,20 +375,18 @@ def compile( placeholder_value = kwargs[placeholder_name] if isinstance(placeholder_value, list): for msg in placeholder_value: - if ( - isinstance(msg, dict) - and "role" in msg - and "content" in msg - ): - compiled_messages.append( - ChatMessageDict( - role=msg["role"], # type: ignore - content=TemplateParser.compile_template( - msg["content"], # type: ignore - kwargs, - ), - ), + if isinstance(msg, dict): + # Preserve all fields from the original message, such as tool calls + compiled_msg = dict(msg) # type: ignore + # Ensure role and content are always present + compiled_msg["role"] = msg.get("role", "NOT_GIVEN") + compiled_msg["content"] = ( + TemplateParser.compile_template( + msg.get("content", ""), # type: ignore + kwargs, + ) ) + compiled_messages.append(compiled_msg) else: compiled_messages.append( ChatMessageDict( diff --git a/tests/test_prompt_compilation.py b/tests/test_prompt_compilation.py index c8aa789dc..10a4cd990 100644 --- a/tests/test_prompt_compilation.py +++ b/tests/test_prompt_compilation.py @@ -850,3 +850,85 @@ def test_get_langchain_prompt_with_unresolved_placeholders(self): # Third message should be the user message assert langchain_messages[2] == ("user", "Help me with coding") + + +def test_tool_calls_preservation_in_message_placeholder(): + """Test that tool calls are preserved when compiling message placeholders.""" + from langfuse.api.resources.prompts import Prompt_Chat + + chat_messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"type": "placeholder", "name": "message_history"}, + {"role": "user", "content": "Help me with {{task}}"}, + ] + + prompt_client = ChatPromptClient( + Prompt_Chat( + type="chat", + name="tool_calls_test", + version=1, + config={}, + tags=[], + labels=[], + prompt=chat_messages, + ) + ) + + # Message history with tool calls - exactly like the bug report describes + message_history_with_tool_calls = [ + {"role": "user", "content": "What's the weather like?"}, + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"location": "San Francisco"}', + }, + } + ], + }, + { + "role": "tool", + "content": "It's sunny, 72°F", + "tool_call_id": "call_123", + "name": "get_weather", + }, + ] + + # Compile with message history and variables + compiled_messages = prompt_client.compile( + task="weather inquiry", message_history=message_history_with_tool_calls + ) + + # Should have 5 messages: system + 3 from history + user + assert len(compiled_messages) == 5 + + # System message + assert compiled_messages[0]["role"] == "system" + assert compiled_messages[0]["content"] == "You are a helpful assistant." + + # User message from history + assert compiled_messages[1]["role"] == "user" + assert compiled_messages[1]["content"] == "What's the weather like?" + + # Assistant message with TOOL CALLS + assert compiled_messages[2]["role"] == "assistant" + assert compiled_messages[2]["content"] == "" + assert "tool_calls" in compiled_messages[2] + assert len(compiled_messages[2]["tool_calls"]) == 1 + assert compiled_messages[2]["tool_calls"][0]["id"] == "call_123" + assert compiled_messages[2]["tool_calls"][0]["function"]["name"] == "get_weather" + + # TOOL CALL results message + assert compiled_messages[3]["role"] == "tool" + assert compiled_messages[3]["content"] == "It's sunny, 72°F" + assert compiled_messages[3]["tool_call_id"] == "call_123" + assert compiled_messages[3]["name"] == "get_weather" + + # Final user message with compiled variable + assert compiled_messages[4]["role"] == "user" + assert compiled_messages[4]["content"] == "Help me with weather inquiry" From 83f1285cd86e36a0406d27b729a28213b18a6f08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:16:15 +0000 Subject: [PATCH 057/296] chore(deps-dev): bump pytest from 8.4.1 to 8.4.2 (#1322) Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.1 to 8.4.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.1...8.4.2) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index d88f11dbb..4387fe601 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1660,14 +1660,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] From 8e546415933b458ec93da61c80e1bade9c890c4f Mon Sep 17 00:00:00 2001 From: jito Date: Thu, 18 Sep 2025 02:12:08 +0900 Subject: [PATCH 058/296] chore(typo): fix minor typos in langfuse client comments (#1350) Signed-off-by: jitokim --- langfuse/_client/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ceb29c5d3..ebc65e988 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -129,7 +129,7 @@ class Langfuse: Attributes: api: Synchronous API client for Langfuse backend communication async_api: Asynchronous API client for Langfuse backend communication - langfuse_tracer: Internal LangfuseTracer instance managing OpenTelemetry components + _otel_tracer: Internal LangfuseTracer instance managing OpenTelemetry components Parameters: public_key (Optional[str]): Your Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable. @@ -1679,7 +1679,7 @@ def update_current_trace( existing_observation_type = current_otel_span.attributes.get( # type: ignore[attr-defined] LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "span" ) - # We need to preserve the class to keep the corret observation type + # We need to preserve the class to keep the correct observation type span_class = self._get_span_class(existing_observation_type) span = span_class( otel_span=current_otel_span, @@ -3134,7 +3134,7 @@ def get_prompt( """ if self._resources is None: raise Error( - "SDK is not correctly initalized. Check the init logs for more details." + "SDK is not correctly initialized. Check the init logs for more details." ) if version is not None and label is not None: raise ValueError("Cannot specify both version and label at the same time.") From 71f443a8bca602e2165fc4baa2de5666aab7cb4f Mon Sep 17 00:00:00 2001 From: Yashwanth <53632453+yash025@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:06:35 +0530 Subject: [PATCH 059/296] feat(client): support custom export path to export spans (#1342) --- langfuse/_client/client.py | 1 + langfuse/_client/environment_variables.py | 9 +++++++++ langfuse/_client/span_processor.py | 11 ++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ebc65e988..aa20f79c3 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -49,6 +49,7 @@ from langfuse._client.environment_variables import ( LANGFUSE_DEBUG, LANGFUSE_HOST, + LANGFUSE_OTEL_TRACES_EXPORT_PATH, LANGFUSE_PUBLIC_KEY, LANGFUSE_SAMPLE_RATE, LANGFUSE_SECRET_KEY, diff --git a/langfuse/_client/environment_variables.py b/langfuse/_client/environment_variables.py index 262fee4d4..d5be09d09 100644 --- a/langfuse/_client/environment_variables.py +++ b/langfuse/_client/environment_variables.py @@ -44,6 +44,15 @@ **Default value:** ``"https://cloud.langfuse.com"`` """ +LANGFUSE_OTEL_TRACES_EXPORT_PATH = "LANGFUSE_OTEL_TRACES_EXPORT_PATH" +""" +.. envvar:: LANGFUSE_OTEL_TRACES_EXPORT_PATH + +URL path on the configured host to export traces to. + +**Default value:** ``/api/public/otel/v1/traces`` +""" + LANGFUSE_DEBUG = "LANGFUSE_DEBUG" """ .. envvar:: LANGFUSE_DEBUG diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index ca8fb9b5a..baa72360c 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -23,6 +23,7 @@ from langfuse._client.environment_variables import ( LANGFUSE_FLUSH_AT, LANGFUSE_FLUSH_INTERVAL, + LANGFUSE_OTEL_TRACES_EXPORT_PATH, ) from langfuse._client.utils import span_formatter from langfuse.logger import langfuse_logger @@ -90,8 +91,16 @@ def __init__( # Merge additional headers if provided headers = {**default_headers, **(additional_headers or {})} + traces_export_path = os.environ.get(LANGFUSE_OTEL_TRACES_EXPORT_PATH, None) + + endpoint = ( + f"{host}/{traces_export_path}" + if traces_export_path + else f"{host}/api/public/otel/v1/traces" + ) + langfuse_span_exporter = OTLPSpanExporter( - endpoint=f"{host}/api/public/otel/v1/traces", + endpoint=endpoint, headers=headers, timeout=timeout, ) From e94f9eaf11439c09108e36754b82a8372480a8c1 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 17 Sep 2025 22:29:33 +0200 Subject: [PATCH 060/296] chore(client): remove unused LANGFUSE_OTEL_TRACES_EXPORT_PATH import --- langfuse/_client/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index aa20f79c3..ebc65e988 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -49,7 +49,6 @@ from langfuse._client.environment_variables import ( LANGFUSE_DEBUG, LANGFUSE_HOST, - LANGFUSE_OTEL_TRACES_EXPORT_PATH, LANGFUSE_PUBLIC_KEY, LANGFUSE_SAMPLE_RATE, LANGFUSE_SECRET_KEY, From 33d908670dcd9f2883a9b4f34d485f0c70ad765a Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 18 Sep 2025 14:18:52 +0200 Subject: [PATCH 061/296] =?UTF-8?q?chore:=20test=20to=20validate=20multipl?= =?UTF-8?q?e=20calls=20to=20update=5Ftrace=20don't=20drop=20att=E2=80=A6?= =?UTF-8?q?=20(#1369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: test to validate multiple calls to update_trace don't drop attributes --- tests/test_core_sdk.py | 124 +++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index f29851d84..26d11746c 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -315,16 +315,56 @@ def test_create_update_trace(): langfuse.flush() sleep(2) - # Ensure trace_id is a string before passing to the API - if trace_id is not None: - # Retrieve and verify trace - trace = get_api().trace.get(trace_id) + assert isinstance(trace_id, str) + # Retrieve and verify trace + trace = get_api().trace.get(trace_id) + + assert trace.name == trace_name + assert trace.user_id == "test" + assert trace.metadata["key"] == "value" + assert trace.metadata["key2"] == "value2" + assert trace.public is False + + +def test_create_update_current_trace(): + langfuse = Langfuse() + + trace_name = create_uuid() + + # Create initial span with trace properties using update_current_trace + with langfuse.start_as_current_span(name="test-span-current") as span: + langfuse.update_current_trace( + name=trace_name, + user_id="test", + metadata={"key": "value"}, + public=True, + input="test_input" + ) + # Get trace ID for later reference + trace_id = span.trace_id - assert trace.name == trace_name - assert trace.user_id == "test" - assert trace.metadata["key"] == "value" - assert trace.metadata["key2"] == "value2" - assert trace.public is False + # Allow a small delay before updating + sleep(1) + + # Update trace properties using update_current_trace + langfuse.update_current_trace(metadata={"key2": "value2"}, public=False, version="1.0") + + # Ensure data is sent to the API + langfuse.flush() + sleep(2) + + assert isinstance(trace_id, str) + # Retrieve and verify trace + trace = get_api().trace.get(trace_id) + + # The 2nd update to the trace must not erase previously set attributes + assert trace.name == trace_name + assert trace.user_id == "test" + assert trace.metadata["key"] == "value" + assert trace.metadata["key2"] == "value2" + assert trace.public is False + assert trace.version == "1.0" + assert trace.input == "test_input" def test_create_generation(): @@ -1917,9 +1957,9 @@ def test_start_as_current_observation_types(): expected_types = {obs_type.upper() for obs_type in observation_types} | { "SPAN" } # includes parent span - assert expected_types.issubset( - found_types - ), f"Missing types: {expected_types - found_types}" + assert expected_types.issubset(found_types), ( + f"Missing types: {expected_types - found_types}" + ) # Verify each specific observation exists for obs_type in observation_types: @@ -1963,25 +2003,25 @@ def test_that_generation_like_properties_are_actually_created(): ) as obs: # Verify the properties are accessible on the observation object if hasattr(obs, "model"): - assert ( - obs.model == test_model - ), f"{obs_type} should have model property" + assert obs.model == test_model, ( + f"{obs_type} should have model property" + ) if hasattr(obs, "completion_start_time"): - assert ( - obs.completion_start_time == test_completion_start_time - ), f"{obs_type} should have completion_start_time property" + assert obs.completion_start_time == test_completion_start_time, ( + f"{obs_type} should have completion_start_time property" + ) if hasattr(obs, "model_parameters"): - assert ( - obs.model_parameters == test_model_parameters - ), f"{obs_type} should have model_parameters property" + assert obs.model_parameters == test_model_parameters, ( + f"{obs_type} should have model_parameters property" + ) if hasattr(obs, "usage_details"): - assert ( - obs.usage_details == test_usage_details - ), f"{obs_type} should have usage_details property" + assert obs.usage_details == test_usage_details, ( + f"{obs_type} should have usage_details property" + ) if hasattr(obs, "cost_details"): - assert ( - obs.cost_details == test_cost_details - ), f"{obs_type} should have cost_details property" + assert obs.cost_details == test_cost_details, ( + f"{obs_type} should have cost_details property" + ) langfuse.flush() @@ -1995,28 +2035,28 @@ def test_that_generation_like_properties_are_actually_created(): for obs in trace.observations if obs.name == f"test-{obs_type}" and obs.type == obs_type.upper() ] - assert ( - len(observations) == 1 - ), f"Expected one {obs_type.upper()} observation, but found {len(observations)}" + assert len(observations) == 1, ( + f"Expected one {obs_type.upper()} observation, but found {len(observations)}" + ) obs = observations[0] assert obs.model == test_model, f"{obs_type} should have model property" - assert ( - obs.model_parameters == test_model_parameters - ), f"{obs_type} should have model_parameters property" + assert obs.model_parameters == test_model_parameters, ( + f"{obs_type} should have model_parameters property" + ) # usage_details assert hasattr(obs, "usage_details"), f"{obs_type} should have usage_details" - assert obs.usage_details == dict( - test_usage_details, total=30 - ), f"{obs_type} should persist usage_details" # API adds total + assert obs.usage_details == dict(test_usage_details, total=30), ( + f"{obs_type} should persist usage_details" + ) # API adds total - assert ( - obs.cost_details == test_cost_details - ), f"{obs_type} should persist cost_details" + assert obs.cost_details == test_cost_details, ( + f"{obs_type} should persist cost_details" + ) # completion_start_time, because of time skew not asserting time - assert ( - obs.completion_start_time is not None - ), f"{obs_type} should persist completion_start_time property" + assert obs.completion_start_time is not None, ( + f"{obs_type} should persist completion_start_time property" + ) From 37468a1320385ddbf4f5685cb0629180da8db659 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 18 Sep 2025 15:16:01 +0200 Subject: [PATCH 062/296] feat(api): update API spec from langfuse/langfuse a7d59d7b (#1371) Co-authored-by: langfuse-bot --- langfuse/api/README.md | 2 +- langfuse/api/__init__.py | 22 + langfuse/api/client.py | 10 + langfuse/api/reference.md | 415 ++++++++++++++- langfuse/api/resources/__init__.py | 24 + .../blob_storage_integrations/__init__.py | 23 + .../blob_storage_integrations/client.py | 492 ++++++++++++++++++ .../types/__init__.py | 23 + .../types/blob_storage_export_frequency.py | 25 + .../types/blob_storage_export_mode.py | 25 + ...b_storage_integration_deletion_response.py | 42 ++ .../blob_storage_integration_file_type.py | 25 + .../blob_storage_integration_response.py | 75 +++ .../types/blob_storage_integration_type.py | 25 + .../blob_storage_integrations_response.py | 43 ++ ...create_blob_storage_integration_request.py | 108 ++++ langfuse/api/resources/ingestion/client.py | 14 +- .../api/resources/organizations/__init__.py | 4 + .../api/resources/organizations/client.py | 322 ++++++++++++ .../resources/organizations/types/__init__.py | 4 + .../types/delete_membership_request.py | 44 ++ .../types/membership_deletion_response.py | 45 ++ langfuse/api/resources/score_v_2/client.py | 10 + langfuse/api/resources/trace/client.py | 4 +- langfuse/api/tests/utils/test_http_client.py | 59 --- .../api/tests/utils/test_query_encoding.py | 19 - 26 files changed, 1813 insertions(+), 91 deletions(-) create mode 100644 langfuse/api/resources/blob_storage_integrations/__init__.py create mode 100644 langfuse/api/resources/blob_storage_integrations/client.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/__init__.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py create mode 100644 langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py create mode 100644 langfuse/api/resources/organizations/types/delete_membership_request.py create mode 100644 langfuse/api/resources/organizations/types/membership_deletion_response.py delete mode 100644 langfuse/api/tests/utils/test_http_client.py delete mode 100644 langfuse/api/tests/utils/test_query_encoding.py diff --git a/langfuse/api/README.md b/langfuse/api/README.md index d7fa24a33..9e8fef6d4 100644 --- a/langfuse/api/README.md +++ b/langfuse/api/README.md @@ -3,7 +3,7 @@ [![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Langfuse%2FPython) [![pypi](https://img.shields.io/pypi/v/langfuse)](https://pypi.python.org/pypi/langfuse) -The Langfuse Python library provides convenient access to the Langfuse API from Python. +The Langfuse Python library provides convenient access to the Langfuse APIs from Python. ## Installation diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 4f43e45f1..932a60e93 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -16,6 +16,13 @@ BasePrompt, BaseScore, BaseScoreV1, + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationDeletionResponse, + BlobStorageIntegrationFileType, + BlobStorageIntegrationResponse, + BlobStorageIntegrationType, + BlobStorageIntegrationsResponse, BooleanScore, BooleanScoreV1, BulkConfig, @@ -32,6 +39,7 @@ CreateAnnotationQueueAssignmentResponse, CreateAnnotationQueueItemRequest, CreateAnnotationQueueRequest, + CreateBlobStorageIntegrationRequest, CreateChatPromptRequest, CreateCommentRequest, CreateCommentResponse, @@ -64,6 +72,7 @@ DeleteAnnotationQueueItemResponse, DeleteDatasetItemResponse, DeleteDatasetRunResponse, + DeleteMembershipRequest, DeleteTraceResponse, EmptyResponse, Error, @@ -101,6 +110,7 @@ LlmConnection, MapValue, MediaContentType, + MembershipDeletionResponse, MembershipRequest, MembershipResponse, MembershipRole, @@ -197,6 +207,7 @@ UsageDetails, UserMeta, annotation_queues, + blob_storage_integrations, comments, commons, dataset_items, @@ -238,6 +249,13 @@ "BasePrompt", "BaseScore", "BaseScoreV1", + "BlobStorageExportFrequency", + "BlobStorageExportMode", + "BlobStorageIntegrationDeletionResponse", + "BlobStorageIntegrationFileType", + "BlobStorageIntegrationResponse", + "BlobStorageIntegrationType", + "BlobStorageIntegrationsResponse", "BooleanScore", "BooleanScoreV1", "BulkConfig", @@ -254,6 +272,7 @@ "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", "CreateAnnotationQueueRequest", + "CreateBlobStorageIntegrationRequest", "CreateChatPromptRequest", "CreateCommentRequest", "CreateCommentResponse", @@ -286,6 +305,7 @@ "DeleteAnnotationQueueItemResponse", "DeleteDatasetItemResponse", "DeleteDatasetRunResponse", + "DeleteMembershipRequest", "DeleteTraceResponse", "EmptyResponse", "Error", @@ -323,6 +343,7 @@ "LlmConnection", "MapValue", "MediaContentType", + "MembershipDeletionResponse", "MembershipRequest", "MembershipResponse", "MembershipRole", @@ -419,6 +440,7 @@ "UsageDetails", "UserMeta", "annotation_queues", + "blob_storage_integrations", "comments", "commons", "dataset_items", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index f18caba1c..619e649fa 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -9,6 +9,10 @@ AnnotationQueuesClient, AsyncAnnotationQueuesClient, ) +from .resources.blob_storage_integrations.client import ( + AsyncBlobStorageIntegrationsClient, + BlobStorageIntegrationsClient, +) from .resources.comments.client import AsyncCommentsClient, CommentsClient from .resources.dataset_items.client import AsyncDatasetItemsClient, DatasetItemsClient from .resources.dataset_run_items.client import ( @@ -116,6 +120,9 @@ def __init__( self.annotation_queues = AnnotationQueuesClient( client_wrapper=self._client_wrapper ) + self.blob_storage_integrations = BlobStorageIntegrationsClient( + client_wrapper=self._client_wrapper + ) self.comments = CommentsClient(client_wrapper=self._client_wrapper) self.dataset_items = DatasetItemsClient(client_wrapper=self._client_wrapper) self.dataset_run_items = DatasetRunItemsClient( @@ -213,6 +220,9 @@ def __init__( self.annotation_queues = AsyncAnnotationQueuesClient( client_wrapper=self._client_wrapper ) + self.blob_storage_integrations = AsyncBlobStorageIntegrationsClient( + client_wrapper=self._client_wrapper + ) self.comments = AsyncCommentsClient(client_wrapper=self._client_wrapper) self.dataset_items = AsyncDatasetItemsClient( client_wrapper=self._client_wrapper diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index ce4c4ecd8..6b243980f 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -854,6 +854,239 @@ client.annotation_queues.delete_queue_assignment(
+ + + + +## BlobStorageIntegrations +
client.blob_storage_integrations.get_blob_storage_integrations() +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get all blob storage integrations for the organization (requires organization-scoped API key) +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.blob_storage_integrations.get_blob_storage_integrations() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.blob_storage_integrations.upsert_blob_storage_integration(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationFileType, + BlobStorageIntegrationType, + CreateBlobStorageIntegrationRequest, +) +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.blob_storage_integrations.upsert_blob_storage_integration( + request=CreateBlobStorageIntegrationRequest( + project_id="projectId", + type=BlobStorageIntegrationType.S_3, + bucket_name="bucketName", + region="region", + export_frequency=BlobStorageExportFrequency.HOURLY, + enabled=True, + force_path_style=True, + file_type=BlobStorageIntegrationFileType.JSON, + export_mode=BlobStorageExportMode.FULL_HISTORY, + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `CreateBlobStorageIntegrationRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.blob_storage_integrations.delete_blob_storage_integration(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Delete a blob storage integration by ID (requires organization-scoped API key) +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.blob_storage_integrations.delete_blob_storage_integration( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -2207,8 +2440,9 @@ client.health.health()
-Batched ingestion for Langfuse Tracing. -If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. +**Legacy endpoint for batch ingestion for Langfuse Observability.** + +-> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. @@ -2218,7 +2452,7 @@ The event.body.id is the ID of the actual trace and will be used for updates and I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: -- Introduction to data model: https://langfuse.com/docs/tracing-data-model +- Introduction to data model: https://langfuse.com/docs/observability/data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors.
@@ -3523,6 +3757,84 @@ client.organizations.update_organization_membership(
+ + + + +
client.organizations.delete_organization_membership(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Delete a membership from the organization associated with the API key (requires organization-scoped API key) +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import DeleteMembershipRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.organizations.delete_organization_membership( + request=DeleteMembershipRequest( + user_id="userId", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `DeleteMembershipRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -3686,6 +3998,93 @@ client.organizations.update_project_membership( + + + + +
client.organizations.delete_project_membership(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import DeleteMembershipRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.organizations.delete_project_membership( + project_id="projectId", + request=DeleteMembershipRequest( + user_id="userId", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**project_id:** `str` + +
+
+ +
+
+ +**request:** `DeleteMembershipRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -5659,6 +6058,14 @@ client.score_v_2.get()
+**session_id:** `typing.Optional[str]` — Retrieve only scores with a specific sessionId. + +
+
+ +
+
+ **queue_id:** `typing.Optional[str]` — Retrieve only scores with a specific annotation queueId.
@@ -6406,7 +6813,7 @@ client.trace.list()
-**fields:** `typing.Optional[str]` — Comma-separated list of fields to include in the response. Available field groups are 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not provided, all fields are included. Example: 'core,scores,metrics' +**fields:** `typing.Optional[str]` — Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'.
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index 062c933be..8b0f6ec76 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -2,6 +2,7 @@ from . import ( annotation_queues, + blob_storage_integrations, comments, commons, dataset_items, @@ -41,6 +42,16 @@ PaginatedAnnotationQueues, UpdateAnnotationQueueItemRequest, ) +from .blob_storage_integrations import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationDeletionResponse, + BlobStorageIntegrationFileType, + BlobStorageIntegrationResponse, + BlobStorageIntegrationType, + BlobStorageIntegrationsResponse, + CreateBlobStorageIntegrationRequest, +) from .comments import CreateCommentRequest, CreateCommentResponse, GetCommentsResponse from .commons import ( AccessDeniedError, @@ -165,6 +176,8 @@ from .models import CreateModelRequest, PaginatedModels from .observations import Observations, ObservationsViews from .organizations import ( + DeleteMembershipRequest, + MembershipDeletionResponse, MembershipRequest, MembershipResponse, MembershipRole, @@ -252,6 +265,13 @@ "BasePrompt", "BaseScore", "BaseScoreV1", + "BlobStorageExportFrequency", + "BlobStorageExportMode", + "BlobStorageIntegrationDeletionResponse", + "BlobStorageIntegrationFileType", + "BlobStorageIntegrationResponse", + "BlobStorageIntegrationType", + "BlobStorageIntegrationsResponse", "BooleanScore", "BooleanScoreV1", "BulkConfig", @@ -268,6 +288,7 @@ "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", "CreateAnnotationQueueRequest", + "CreateBlobStorageIntegrationRequest", "CreateChatPromptRequest", "CreateCommentRequest", "CreateCommentResponse", @@ -300,6 +321,7 @@ "DeleteAnnotationQueueItemResponse", "DeleteDatasetItemResponse", "DeleteDatasetRunResponse", + "DeleteMembershipRequest", "DeleteTraceResponse", "EmptyResponse", "Error", @@ -337,6 +359,7 @@ "LlmConnection", "MapValue", "MediaContentType", + "MembershipDeletionResponse", "MembershipRequest", "MembershipResponse", "MembershipRole", @@ -433,6 +456,7 @@ "UsageDetails", "UserMeta", "annotation_queues", + "blob_storage_integrations", "comments", "commons", "dataset_items", diff --git a/langfuse/api/resources/blob_storage_integrations/__init__.py b/langfuse/api/resources/blob_storage_integrations/__init__.py new file mode 100644 index 000000000..a635fba57 --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/__init__.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationDeletionResponse, + BlobStorageIntegrationFileType, + BlobStorageIntegrationResponse, + BlobStorageIntegrationType, + BlobStorageIntegrationsResponse, + CreateBlobStorageIntegrationRequest, +) + +__all__ = [ + "BlobStorageExportFrequency", + "BlobStorageExportMode", + "BlobStorageIntegrationDeletionResponse", + "BlobStorageIntegrationFileType", + "BlobStorageIntegrationResponse", + "BlobStorageIntegrationType", + "BlobStorageIntegrationsResponse", + "CreateBlobStorageIntegrationRequest", +] diff --git a/langfuse/api/resources/blob_storage_integrations/client.py b/langfuse/api/resources/blob_storage_integrations/client.py new file mode 100644 index 000000000..73aec4fa4 --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/client.py @@ -0,0 +1,492 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from .types.blob_storage_integration_deletion_response import ( + BlobStorageIntegrationDeletionResponse, +) +from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse +from .types.create_blob_storage_integration_request import ( + CreateBlobStorageIntegrationRequest, +) + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BlobStorageIntegrationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_blob_storage_integrations( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationsResponse: + """ + Get all blob storage integrations for the organization (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationsResponse + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.get_blob_storage_integrations() + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + BlobStorageIntegrationsResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def upsert_blob_storage_integration( + self, + *, + request: CreateBlobStorageIntegrationRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> BlobStorageIntegrationResponse: + """ + Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. + + Parameters + ---------- + request : CreateBlobStorageIntegrationRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationResponse + + Examples + -------- + from langfuse import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationFileType, + BlobStorageIntegrationType, + CreateBlobStorageIntegrationRequest, + ) + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.upsert_blob_storage_integration( + request=CreateBlobStorageIntegrationRequest( + project_id="projectId", + type=BlobStorageIntegrationType.S_3, + bucket_name="bucketName", + region="region", + export_frequency=BlobStorageExportFrequency.HOURLY, + enabled=True, + force_path_style=True, + file_type=BlobStorageIntegrationFileType.JSON, + export_mode=BlobStorageExportMode.FULL_HISTORY, + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="PUT", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + BlobStorageIntegrationResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def delete_blob_storage_integration( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationDeletionResponse: + """ + Delete a blob storage integration by ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationDeletionResponse + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.delete_blob_storage_integration( + id="id", + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + BlobStorageIntegrationDeletionResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncBlobStorageIntegrationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_blob_storage_integrations( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationsResponse: + """ + Get all blob storage integrations for the organization (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationsResponse + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.get_blob_storage_integrations() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + BlobStorageIntegrationsResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def upsert_blob_storage_integration( + self, + *, + request: CreateBlobStorageIntegrationRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> BlobStorageIntegrationResponse: + """ + Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. + + Parameters + ---------- + request : CreateBlobStorageIntegrationRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationResponse + + Examples + -------- + import asyncio + + from langfuse import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationFileType, + BlobStorageIntegrationType, + CreateBlobStorageIntegrationRequest, + ) + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.upsert_blob_storage_integration( + request=CreateBlobStorageIntegrationRequest( + project_id="projectId", + type=BlobStorageIntegrationType.S_3, + bucket_name="bucketName", + region="region", + export_frequency=BlobStorageExportFrequency.HOURLY, + enabled=True, + force_path_style=True, + file_type=BlobStorageIntegrationFileType.JSON, + export_mode=BlobStorageExportMode.FULL_HISTORY, + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="PUT", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + BlobStorageIntegrationResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def delete_blob_storage_integration( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationDeletionResponse: + """ + Delete a blob storage integration by ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationDeletionResponse + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.delete_blob_storage_integration( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + BlobStorageIntegrationDeletionResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/blob_storage_integrations/types/__init__.py b/langfuse/api/resources/blob_storage_integrations/types/__init__.py new file mode 100644 index 000000000..621196c11 --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/__init__.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from .blob_storage_export_frequency import BlobStorageExportFrequency +from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_integration_deletion_response import ( + BlobStorageIntegrationDeletionResponse, +) +from .blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .blob_storage_integration_response import BlobStorageIntegrationResponse +from .blob_storage_integration_type import BlobStorageIntegrationType +from .blob_storage_integrations_response import BlobStorageIntegrationsResponse +from .create_blob_storage_integration_request import CreateBlobStorageIntegrationRequest + +__all__ = [ + "BlobStorageExportFrequency", + "BlobStorageExportMode", + "BlobStorageIntegrationDeletionResponse", + "BlobStorageIntegrationFileType", + "BlobStorageIntegrationResponse", + "BlobStorageIntegrationType", + "BlobStorageIntegrationsResponse", + "CreateBlobStorageIntegrationRequest", +] diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py new file mode 100644 index 000000000..936e0c18f --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageExportFrequency(str, enum.Enum): + HOURLY = "hourly" + DAILY = "daily" + WEEKLY = "weekly" + + def visit( + self, + hourly: typing.Callable[[], T_Result], + daily: typing.Callable[[], T_Result], + weekly: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageExportFrequency.HOURLY: + return hourly() + if self is BlobStorageExportFrequency.DAILY: + return daily() + if self is BlobStorageExportFrequency.WEEKLY: + return weekly() diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py new file mode 100644 index 000000000..1eafab79d --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageExportMode(str, enum.Enum): + FULL_HISTORY = "FULL_HISTORY" + FROM_TODAY = "FROM_TODAY" + FROM_CUSTOM_DATE = "FROM_CUSTOM_DATE" + + def visit( + self, + full_history: typing.Callable[[], T_Result], + from_today: typing.Callable[[], T_Result], + from_custom_date: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageExportMode.FULL_HISTORY: + return full_history() + if self is BlobStorageExportMode.FROM_TODAY: + return from_today() + if self is BlobStorageExportMode.FROM_CUSTOM_DATE: + return from_custom_date() diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py new file mode 100644 index 000000000..4305cff2f --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class BlobStorageIntegrationDeletionResponse(pydantic_v1.BaseModel): + message: str + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py new file mode 100644 index 000000000..a63631c6f --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageIntegrationFileType(str, enum.Enum): + JSON = "JSON" + CSV = "CSV" + JSONL = "JSONL" + + def visit( + self, + json: typing.Callable[[], T_Result], + csv: typing.Callable[[], T_Result], + jsonl: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageIntegrationFileType.JSON: + return json() + if self is BlobStorageIntegrationFileType.CSV: + return csv() + if self is BlobStorageIntegrationFileType.JSONL: + return jsonl() diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py new file mode 100644 index 000000000..e308e8113 --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .blob_storage_export_frequency import BlobStorageExportFrequency +from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .blob_storage_integration_type import BlobStorageIntegrationType + + +class BlobStorageIntegrationResponse(pydantic_v1.BaseModel): + id: str + project_id: str = pydantic_v1.Field(alias="projectId") + type: BlobStorageIntegrationType + bucket_name: str = pydantic_v1.Field(alias="bucketName") + endpoint: typing.Optional[str] = None + region: str + access_key_id: typing.Optional[str] = pydantic_v1.Field( + alias="accessKeyId", default=None + ) + prefix: str + export_frequency: BlobStorageExportFrequency = pydantic_v1.Field( + alias="exportFrequency" + ) + enabled: bool + force_path_style: bool = pydantic_v1.Field(alias="forcePathStyle") + file_type: BlobStorageIntegrationFileType = pydantic_v1.Field(alias="fileType") + export_mode: BlobStorageExportMode = pydantic_v1.Field(alias="exportMode") + export_start_date: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="exportStartDate", default=None + ) + next_sync_at: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="nextSyncAt", default=None + ) + last_sync_at: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="lastSyncAt", default=None + ) + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") + updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py new file mode 100644 index 000000000..38bacbf85 --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageIntegrationType(str, enum.Enum): + S_3 = "S3" + S_3_COMPATIBLE = "S3_COMPATIBLE" + AZURE_BLOB_STORAGE = "AZURE_BLOB_STORAGE" + + def visit( + self, + s_3: typing.Callable[[], T_Result], + s_3_compatible: typing.Callable[[], T_Result], + azure_blob_storage: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageIntegrationType.S_3: + return s_3() + if self is BlobStorageIntegrationType.S_3_COMPATIBLE: + return s_3_compatible() + if self is BlobStorageIntegrationType.AZURE_BLOB_STORAGE: + return azure_blob_storage() diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py new file mode 100644 index 000000000..c6231a23e --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .blob_storage_integration_response import BlobStorageIntegrationResponse + + +class BlobStorageIntegrationsResponse(pydantic_v1.BaseModel): + data: typing.List[BlobStorageIntegrationResponse] + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py new file mode 100644 index 000000000..31b5779c6 --- /dev/null +++ b/langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py @@ -0,0 +1,108 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .blob_storage_export_frequency import BlobStorageExportFrequency +from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .blob_storage_integration_type import BlobStorageIntegrationType + + +class CreateBlobStorageIntegrationRequest(pydantic_v1.BaseModel): + project_id: str = pydantic_v1.Field(alias="projectId") + """ + ID of the project in which to configure the blob storage integration + """ + + type: BlobStorageIntegrationType + bucket_name: str = pydantic_v1.Field(alias="bucketName") + """ + Name of the storage bucket + """ + + endpoint: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Custom endpoint URL (required for S3_COMPATIBLE type) + """ + + region: str = pydantic_v1.Field() + """ + Storage region + """ + + access_key_id: typing.Optional[str] = pydantic_v1.Field( + alias="accessKeyId", default=None + ) + """ + Access key ID for authentication + """ + + secret_access_key: typing.Optional[str] = pydantic_v1.Field( + alias="secretAccessKey", default=None + ) + """ + Secret access key for authentication (will be encrypted when stored) + """ + + prefix: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Path prefix for exported files (must end with forward slash if provided) + """ + + export_frequency: BlobStorageExportFrequency = pydantic_v1.Field( + alias="exportFrequency" + ) + enabled: bool = pydantic_v1.Field() + """ + Whether the integration is active + """ + + force_path_style: bool = pydantic_v1.Field(alias="forcePathStyle") + """ + Use path-style URLs for S3 requests + """ + + file_type: BlobStorageIntegrationFileType = pydantic_v1.Field(alias="fileType") + export_mode: BlobStorageExportMode = pydantic_v1.Field(alias="exportMode") + export_start_date: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="exportStartDate", default=None + ) + """ + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/client.py b/langfuse/api/resources/ingestion/client.py index 9d6784856..d5aa2f952 100644 --- a/langfuse/api/resources/ingestion/client.py +++ b/langfuse/api/resources/ingestion/client.py @@ -31,8 +31,9 @@ def batch( request_options: typing.Optional[RequestOptions] = None, ) -> IngestionResponse: """ - Batched ingestion for Langfuse Tracing. - If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. + **Legacy endpoint for batch ingestion for Langfuse Observability.** + + -> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. @@ -42,7 +43,7 @@ def batch( I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - - Introduction to data model: https://langfuse.com/docs/tracing-data-model + - Introduction to data model: https://langfuse.com/docs/observability/data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. @@ -148,8 +149,9 @@ async def batch( request_options: typing.Optional[RequestOptions] = None, ) -> IngestionResponse: """ - Batched ingestion for Langfuse Tracing. - If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. + **Legacy endpoint for batch ingestion for Langfuse Observability.** + + -> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. @@ -159,7 +161,7 @@ async def batch( I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - - Introduction to data model: https://langfuse.com/docs/tracing-data-model + - Introduction to data model: https://langfuse.com/docs/observability/data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. diff --git a/langfuse/api/resources/organizations/__init__.py b/langfuse/api/resources/organizations/__init__.py index 48edda3f4..5c5bfced3 100644 --- a/langfuse/api/resources/organizations/__init__.py +++ b/langfuse/api/resources/organizations/__init__.py @@ -1,6 +1,8 @@ # This file was auto-generated by Fern from our API Definition. from .types import ( + DeleteMembershipRequest, + MembershipDeletionResponse, MembershipRequest, MembershipResponse, MembershipRole, @@ -10,6 +12,8 @@ ) __all__ = [ + "DeleteMembershipRequest", + "MembershipDeletionResponse", "MembershipRequest", "MembershipResponse", "MembershipRole", diff --git a/langfuse/api/resources/organizations/client.py b/langfuse/api/resources/organizations/client.py index f7f2f5021..b60f2d2bd 100644 --- a/langfuse/api/resources/organizations/client.py +++ b/langfuse/api/resources/organizations/client.py @@ -13,6 +13,8 @@ from ..commons.errors.method_not_allowed_error import MethodNotAllowedError from ..commons.errors.not_found_error import NotFoundError from ..commons.errors.unauthorized_error import UnauthorizedError +from .types.delete_membership_request import DeleteMembershipRequest +from .types.membership_deletion_response import MembershipDeletionResponse from .types.membership_request import MembershipRequest from .types.membership_response import MembershipResponse from .types.memberships_response import MembershipsResponse @@ -159,6 +161,80 @@ def update_organization_membership( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def delete_organization_membership( + self, + *, + request: DeleteMembershipRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipDeletionResponse: + """ + Delete a membership from the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request : DeleteMembershipRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + from langfuse import DeleteMembershipRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.delete_organization_membership( + request=DeleteMembershipRequest( + user_id="userId", + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="DELETE", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + MembershipDeletionResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + def get_project_memberships( self, project_id: str, @@ -303,6 +379,84 @@ def update_project_membership( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def delete_project_membership( + self, + project_id: str, + *, + request: DeleteMembershipRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipDeletionResponse: + """ + Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. + + Parameters + ---------- + project_id : str + + request : DeleteMembershipRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + from langfuse import DeleteMembershipRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.delete_project_membership( + project_id="projectId", + request=DeleteMembershipRequest( + user_id="userId", + ), + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="DELETE", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + MembershipDeletionResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + def get_organization_projects( self, *, request_options: typing.Optional[RequestOptions] = None ) -> OrganizationProjectsResponse: @@ -519,6 +673,88 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + async def delete_organization_membership( + self, + *, + request: DeleteMembershipRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipDeletionResponse: + """ + Delete a membership from the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request : DeleteMembershipRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import DeleteMembershipRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.delete_organization_membership( + request=DeleteMembershipRequest( + user_id="userId", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="DELETE", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + MembershipDeletionResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + async def get_project_memberships( self, project_id: str, @@ -679,6 +915,92 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + async def delete_project_membership( + self, + project_id: str, + *, + request: DeleteMembershipRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipDeletionResponse: + """ + Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. + + Parameters + ---------- + project_id : str + + request : DeleteMembershipRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import DeleteMembershipRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.delete_project_membership( + project_id="projectId", + request=DeleteMembershipRequest( + user_id="userId", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="DELETE", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + MembershipDeletionResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + async def get_organization_projects( self, *, request_options: typing.Optional[RequestOptions] = None ) -> OrganizationProjectsResponse: diff --git a/langfuse/api/resources/organizations/types/__init__.py b/langfuse/api/resources/organizations/types/__init__.py index 4a401124d..d154f63d8 100644 --- a/langfuse/api/resources/organizations/types/__init__.py +++ b/langfuse/api/resources/organizations/types/__init__.py @@ -1,5 +1,7 @@ # This file was auto-generated by Fern from our API Definition. +from .delete_membership_request import DeleteMembershipRequest +from .membership_deletion_response import MembershipDeletionResponse from .membership_request import MembershipRequest from .membership_response import MembershipResponse from .membership_role import MembershipRole @@ -8,6 +10,8 @@ from .organization_projects_response import OrganizationProjectsResponse __all__ = [ + "DeleteMembershipRequest", + "MembershipDeletionResponse", "MembershipRequest", "MembershipResponse", "MembershipRole", diff --git a/langfuse/api/resources/organizations/types/delete_membership_request.py b/langfuse/api/resources/organizations/types/delete_membership_request.py new file mode 100644 index 000000000..6752b0aae --- /dev/null +++ b/langfuse/api/resources/organizations/types/delete_membership_request.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class DeleteMembershipRequest(pydantic_v1.BaseModel): + user_id: str = pydantic_v1.Field(alias="userId") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/membership_deletion_response.py b/langfuse/api/resources/organizations/types/membership_deletion_response.py new file mode 100644 index 000000000..f9c1915b7 --- /dev/null +++ b/langfuse/api/resources/organizations/types/membership_deletion_response.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class MembershipDeletionResponse(pydantic_v1.BaseModel): + message: str + user_id: str = pydantic_v1.Field(alias="userId") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/client.py b/langfuse/api/resources/score_v_2/client.py index 894b44f22..e927b6c2b 100644 --- a/langfuse/api/resources/score_v_2/client.py +++ b/langfuse/api/resources/score_v_2/client.py @@ -40,6 +40,7 @@ def get( value: typing.Optional[float] = None, score_ids: typing.Optional[str] = None, config_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, @@ -86,6 +87,9 @@ def get( config_id : typing.Optional[str] Retrieve only scores with a specific configId. + session_id : typing.Optional[str] + Retrieve only scores with a specific sessionId. + queue_id : typing.Optional[str] Retrieve only scores with a specific annotation queueId. @@ -136,6 +140,7 @@ def get( "value": value, "scoreIds": score_ids, "configId": config_id, + "sessionId": session_id, "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, @@ -253,6 +258,7 @@ async def get( value: typing.Optional[float] = None, score_ids: typing.Optional[str] = None, config_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, @@ -299,6 +305,9 @@ async def get( config_id : typing.Optional[str] Retrieve only scores with a specific configId. + session_id : typing.Optional[str] + Retrieve only scores with a specific sessionId. + queue_id : typing.Optional[str] Retrieve only scores with a specific annotation queueId. @@ -357,6 +366,7 @@ async def main() -> None: "value": value, "scoreIds": score_ids, "configId": config_id, + "sessionId": session_id, "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, diff --git a/langfuse/api/resources/trace/client.py b/langfuse/api/resources/trace/client.py index c73901123..824142a27 100644 --- a/langfuse/api/resources/trace/client.py +++ b/langfuse/api/resources/trace/client.py @@ -214,7 +214,7 @@ def list( Optional filter for traces where the environment is one of the provided values. fields : typing.Optional[str] - Comma-separated list of fields to include in the response. Available field groups are 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not provided, all fields are included. Example: 'core,scores,metrics' + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -565,7 +565,7 @@ async def list( Optional filter for traces where the environment is one of the provided values. fields : typing.Optional[str] - Comma-separated list of fields to include in the response. Available field groups are 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not provided, all fields are included. Example: 'core,scores,metrics' + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/tests/utils/test_http_client.py b/langfuse/api/tests/utils/test_http_client.py deleted file mode 100644 index 950fcdeb1..000000000 --- a/langfuse/api/tests/utils/test_http_client.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from langfuse.api.core.http_client import get_request_body -from langfuse.api.core.request_options import RequestOptions - - -def get_request_options() -> RequestOptions: - return {"additional_body_parameters": {"see you": "later"}} - - -def test_get_json_request_body() -> None: - json_body, data_body = get_request_body( - json={"hello": "world"}, data=None, request_options=None, omit=None - ) - assert json_body == {"hello": "world"} - assert data_body is None - - json_body_extras, data_body_extras = get_request_body( - json={"goodbye": "world"}, - data=None, - request_options=get_request_options(), - omit=None, - ) - - assert json_body_extras == {"goodbye": "world", "see you": "later"} - assert data_body_extras is None - - -def test_get_files_request_body() -> None: - json_body, data_body = get_request_body( - json=None, data={"hello": "world"}, request_options=None, omit=None - ) - assert data_body == {"hello": "world"} - assert json_body is None - - json_body_extras, data_body_extras = get_request_body( - json=None, - data={"goodbye": "world"}, - request_options=get_request_options(), - omit=None, - ) - - assert data_body_extras == {"goodbye": "world", "see you": "later"} - assert json_body_extras is None - - -def test_get_none_request_body() -> None: - json_body, data_body = get_request_body( - json=None, data=None, request_options=None, omit=None - ) - assert data_body is None - assert json_body is None - - json_body_extras, data_body_extras = get_request_body( - json=None, data=None, request_options=get_request_options(), omit=None - ) - - assert json_body_extras == {"see you": "later"} - assert data_body_extras is None diff --git a/langfuse/api/tests/utils/test_query_encoding.py b/langfuse/api/tests/utils/test_query_encoding.py deleted file mode 100644 index 9afa0ea78..000000000 --- a/langfuse/api/tests/utils/test_query_encoding.py +++ /dev/null @@ -1,19 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from langfuse.api.core.query_encoder import encode_query - - -def test_query_encoding() -> None: - assert encode_query({"hello world": "hello world"}) == { - "hello world": "hello world" - } - assert encode_query({"hello_world": {"hello": "world"}}) == { - "hello_world[hello]": "world" - } - assert encode_query( - {"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"} - ) == { - "hello_world[hello][world]": "today", - "hello_world[test]": "this", - "hi": "there", - } From f809baf7069e491f28426eebfd6afdd3580d9ac4 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:56:10 +0200 Subject: [PATCH 063/296] feat(openai): add openai embeddings api support (#1345) (#1372) --- langfuse/openai.py | 105 +++++++++++++++++++++++++++++++++++-------- tests/test_openai.py | 90 +++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 19 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index d7b0ecd85..1a63835d4 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -177,6 +177,20 @@ class OpenAiDefinition: sync=False, min_version="1.66.0", ), + OpenAiDefinition( + module="openai.resources.embeddings", + object="Embeddings", + method="create", + type="embedding", + sync=True, + ), + OpenAiDefinition( + module="openai.resources.embeddings", + object="AsyncEmbeddings", + method="create", + type="embedding", + sync=False, + ), ] @@ -340,10 +354,13 @@ def _extract_chat_response(kwargs: Any) -> Any: def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> Any: - name = kwargs.get("name", "OpenAI-generation") + default_name = ( + "OpenAI-embedding" if resource.type == "embedding" else "OpenAI-generation" + ) + name = kwargs.get("name", default_name) if name is None: - name = "OpenAI-generation" + name = default_name if name is not None and not isinstance(name, str): raise TypeError("name must be a string") @@ -395,6 +412,8 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A prompt = kwargs.get("input", None) elif resource.type == "chat": prompt = _extract_chat_prompt(kwargs) + elif resource.type == "embedding": + prompt = kwargs.get("input", None) parsed_temperature = ( kwargs.get("temperature", 1) @@ -440,23 +459,41 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A parsed_n = kwargs.get("n", 1) if not isinstance(kwargs.get("n", 1), NotGiven) else 1 - modelParameters = { - "temperature": parsed_temperature, - "max_tokens": parsed_max_tokens, # casing? - "top_p": parsed_top_p, - "frequency_penalty": parsed_frequency_penalty, - "presence_penalty": parsed_presence_penalty, - } + if resource.type == "embedding": + parsed_dimensions = ( + kwargs.get("dimensions", None) + if not isinstance(kwargs.get("dimensions", None), NotGiven) + else None + ) + parsed_encoding_format = ( + kwargs.get("encoding_format", "float") + if not isinstance(kwargs.get("encoding_format", "float"), NotGiven) + else "float" + ) - if parsed_max_completion_tokens is not None: - modelParameters.pop("max_tokens", None) - modelParameters["max_completion_tokens"] = parsed_max_completion_tokens + modelParameters = {} + if parsed_dimensions is not None: + modelParameters["dimensions"] = parsed_dimensions + if parsed_encoding_format != "float": + modelParameters["encoding_format"] = parsed_encoding_format + else: + modelParameters = { + "temperature": parsed_temperature, + "max_tokens": parsed_max_tokens, + "top_p": parsed_top_p, + "frequency_penalty": parsed_frequency_penalty, + "presence_penalty": parsed_presence_penalty, + } - if parsed_n is not None and parsed_n > 1: - modelParameters["n"] = parsed_n + if parsed_max_completion_tokens is not None: + modelParameters.pop("max_tokens", None) + modelParameters["max_completion_tokens"] = parsed_max_completion_tokens - if parsed_seed is not None: - modelParameters["seed"] = parsed_seed + if parsed_n is not None and parsed_n > 1: + modelParameters["n"] = parsed_n + + if parsed_seed is not None: + modelParameters["seed"] = parsed_seed langfuse_prompt = kwargs.get("langfuse_prompt", None) @@ -521,6 +558,14 @@ def _parse_usage(usage: Optional[Any] = None) -> Any: k: v for k, v in tokens_details_dict.items() if v is not None } + if ( + len(usage_dict) == 2 + and "prompt_tokens" in usage_dict + and "total_tokens" in usage_dict + ): + # handle embedding usage + return {"input": usage_dict["prompt_tokens"]} + return usage_dict @@ -646,7 +691,7 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: curr[-1]["arguments"] = "" curr[-1]["arguments"] += getattr( - tool_call_chunk, "arguments", None + tool_call_chunk, "arguments", "" ) if resource.type == "completion": @@ -729,6 +774,20 @@ def _get_langfuse_data_from_default_response( else choice.get("message", None) ) + elif resource.type == "embedding": + data = response.get("data", []) + if len(data) > 0: + first_embedding = data[0] + embedding_vector = ( + first_embedding.embedding + if hasattr(first_embedding, "embedding") + else first_embedding.get("embedding", []) + ) + completion = { + "dimensions": len(embedding_vector) if embedding_vector else 0, + "count": len(data), + } + usage = _parse_usage(response.get("usage", None)) return (model, completion, usage) @@ -757,8 +816,12 @@ def _wrap( langfuse_data = _get_langfuse_data_from_kwargs(open_ai_resource, langfuse_args) langfuse_client = get_client(public_key=langfuse_args["langfuse_public_key"]) + observation_type = ( + "embedding" if open_ai_resource.type == "embedding" else "generation" + ) + generation = langfuse_client.start_observation( - as_type="generation", + as_type=observation_type, # type: ignore name=langfuse_data["name"], input=langfuse_data.get("input", None), metadata=langfuse_data.get("metadata", None), @@ -824,8 +887,12 @@ async def _wrap_async( langfuse_data = _get_langfuse_data_from_kwargs(open_ai_resource, langfuse_args) langfuse_client = get_client(public_key=langfuse_args["langfuse_public_key"]) + observation_type = ( + "embedding" if open_ai_resource.type == "embedding" else "generation" + ) + generation = langfuse_client.start_observation( - as_type="generation", + as_type=observation_type, # type: ignore name=langfuse_data["name"], input=langfuse_data.get("input", None), metadata=langfuse_data.get("metadata", None), diff --git a/tests/test_openai.py b/tests/test_openai.py index 056e4597d..b6bcf29d6 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -1514,3 +1514,93 @@ def test_response_api_reasoning(openai): assert generationData.usage.total is not None assert generationData.output is not None assert generationData.metadata is not None + + +def test_openai_embeddings(openai): + embedding_name = create_uuid() + openai.OpenAI().embeddings.create( + name=embedding_name, + model="text-embedding-ada-002", + input="The quick brown fox jumps over the lazy dog", + metadata={"test_key": "test_value"}, + ) + + langfuse.flush() + sleep(1) + + embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + + assert len(embedding.data) != 0 + embedding_data = embedding.data[0] + assert embedding_data.name == embedding_name + assert embedding_data.metadata["test_key"] == "test_value" + assert embedding_data.input == "The quick brown fox jumps over the lazy dog" + assert embedding_data.type == "EMBEDDING" + assert "text-embedding-ada-002" in embedding_data.model + assert embedding_data.start_time is not None + assert embedding_data.end_time is not None + assert embedding_data.start_time < embedding_data.end_time + assert embedding_data.usage.input is not None + assert embedding_data.usage.total is not None + assert embedding_data.output is not None + assert "dimensions" in embedding_data.output + assert "count" in embedding_data.output + assert embedding_data.output["count"] == 1 + + +def test_openai_embeddings_multiple_inputs(openai): + embedding_name = create_uuid() + inputs = ["The quick brown fox", "jumps over the lazy dog", "Hello world"] + + openai.OpenAI().embeddings.create( + name=embedding_name, + model="text-embedding-ada-002", + input=inputs, + metadata={"batch_size": len(inputs)}, + ) + + langfuse.flush() + sleep(1) + + embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + + assert len(embedding.data) != 0 + embedding_data = embedding.data[0] + assert embedding_data.name == embedding_name + assert embedding_data.input == inputs + assert embedding_data.type == "EMBEDDING" + assert "text-embedding-ada-002" in embedding_data.model + assert embedding_data.usage.input is not None + assert embedding_data.usage.total is not None + assert embedding_data.output["count"] == len(inputs) + + +@pytest.mark.asyncio +async def test_async_openai_embeddings(openai): + client = openai.AsyncOpenAI() + embedding_name = create_uuid() + print(embedding_name) + + result = await client.embeddings.create( + name=embedding_name, + model="text-embedding-ada-002", + input="Async embedding test", + metadata={"async": True}, + ) + + print("result:", result.usage) + + langfuse.flush() + sleep(1) + + embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + + assert len(embedding.data) != 0 + embedding_data = embedding.data[0] + assert embedding_data.name == embedding_name + assert embedding_data.input == "Async embedding test" + assert embedding_data.type == "EMBEDDING" + assert "text-embedding-ada-002" in embedding_data.model + assert embedding_data.metadata["async"] is True + assert embedding_data.usage.input is not None + assert embedding_data.usage.total is not None From c8550a79cee11b03528f340aa7844cf8a90ec16c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:56:50 +0200 Subject: [PATCH 064/296] chore: release v3.5.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 28deeaff8..b6dc9419b 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.4.0" +__version__ = "3.5.0" diff --git a/pyproject.toml b/pyproject.toml index ff5ebafac..d969034e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.4.0" +version = "3.5.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From d0fa566717eab64f8f09203712b54886b6eb2061 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:34:46 +0200 Subject: [PATCH 065/296] fix(langchain): maintain async context in tool executions (#1380) --- langfuse/langchain/CallbackHandler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index b4c27c3bc..98ef24680 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -78,6 +78,7 @@ def __init__( update_trace: Whether to update the Langfuse trace with the chains input / output / metadata / name. Defaults to False. """ self.client = get_client(public_key=public_key) + self.run_inline = True self.runs: Dict[ UUID, From 3ce7abe05dc8d6b5299a5ea6ac809192dcee2984 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:35:08 +0200 Subject: [PATCH 066/296] chore: release v3.5.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index b6dc9419b..74415ba37 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.5.0" +__version__ = "3.5.1" diff --git a/pyproject.toml b/pyproject.toml index d969034e1..bfa63ced0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.5.0" +version = "3.5.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From d02d9401d8f6f95a9dce04fbd0adaf89c4e65a37 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:14:09 +0200 Subject: [PATCH 067/296] fix(observe): handle generator context propagation (#1383) --- langfuse/_client/observe.py | 183 +++++++++++++++++------ tests/test_decorators.py | 281 ++++++++++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+), 41 deletions(-) diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index ce848e04a..6b9d52278 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -1,4 +1,5 @@ import asyncio +import contextvars import inspect import logging import os @@ -10,6 +11,7 @@ Dict, Generator, Iterable, + List, Optional, Tuple, TypeVar, @@ -21,25 +23,24 @@ from opentelemetry.util._decorator import _AgnosticContextManager from typing_extensions import ParamSpec -from langfuse._client.environment_variables import ( - LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, -) - from langfuse._client.constants import ( ObservationTypeLiteralNoEvent, get_observation_types_list, ) +from langfuse._client.environment_variables import ( + LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, +) from langfuse._client.get_client import _set_current_public_key, get_client from langfuse._client.span import ( - LangfuseGeneration, - LangfuseSpan, LangfuseAgent, - LangfuseTool, LangfuseChain, - LangfuseRetriever, - LangfuseEvaluator, LangfuseEmbedding, + LangfuseEvaluator, + LangfuseGeneration, LangfuseGuardrail, + LangfuseRetriever, + LangfuseSpan, + LangfuseTool, ) from langfuse.types import TraceContext @@ -468,29 +469,54 @@ def _wrap_sync_generator_result( generator: Generator, transform_to_string: Optional[Callable[[Iterable], str]] = None, ) -> Any: - items = [] + preserved_context = contextvars.copy_context() - try: - for item in generator: - items.append(item) + return _ContextPreservedSyncGeneratorWrapper( + generator, + preserved_context, + langfuse_span_or_generation, + transform_to_string, + ) + + def _wrap_async_generator_result( + self, + langfuse_span_or_generation: Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ], + generator: AsyncGenerator, + transform_to_string: Optional[Callable[[Iterable], str]] = None, + ) -> Any: + preserved_context = contextvars.copy_context() - yield item + return _ContextPreservedAsyncGeneratorWrapper( + generator, + preserved_context, + langfuse_span_or_generation, + transform_to_string, + ) - finally: - output: Any = items - if transform_to_string is not None: - output = transform_to_string(items) +_decorator = LangfuseDecorator() + +observe = _decorator.observe - elif all(isinstance(item, str) for item in items): - output = "".join(items) - langfuse_span_or_generation.update(output=output) - langfuse_span_or_generation.end() +class _ContextPreservedSyncGeneratorWrapper: + """Sync generator wrapper that ensures each iteration runs in preserved context.""" - async def _wrap_async_generator_result( + def __init__( self, - langfuse_span_or_generation: Union[ + generator: Generator, + context: contextvars.Context, + span: Union[ LangfuseSpan, LangfuseGeneration, LangfuseAgent, @@ -501,30 +527,105 @@ async def _wrap_async_generator_result( LangfuseEmbedding, LangfuseGuardrail, ], - generator: AsyncGenerator, - transform_to_string: Optional[Callable[[Iterable], str]] = None, - ) -> AsyncGenerator: - items = [] + transform_fn: Optional[Callable[[Iterable], str]], + ) -> None: + self.generator = generator + self.context = context + self.items: List[Any] = [] + self.span = span + self.transform_fn = transform_fn + + def __iter__(self) -> "_ContextPreservedSyncGeneratorWrapper": + return self + + def __next__(self) -> Any: + try: + # Run the generator's __next__ in the preserved context + item = self.context.run(next, self.generator) + self.items.append(item) + + return item + + except StopIteration: + # Handle output and span cleanup when generator is exhausted + output: Any = self.items + + if self.transform_fn is not None: + output = self.transform_fn(self.items) + + elif all(isinstance(item, str) for item in self.items): + output = "".join(self.items) + + self.span.update(output=output).end() + + raise # Re-raise StopIteration + + except Exception as e: + self.span.update(level="ERROR", status_message=str(e)).end() + raise + + +class _ContextPreservedAsyncGeneratorWrapper: + """Async generator wrapper that ensures each iteration runs in preserved context.""" + + def __init__( + self, + generator: AsyncGenerator, + context: contextvars.Context, + span: Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ], + transform_fn: Optional[Callable[[Iterable], str]], + ) -> None: + self.generator = generator + self.context = context + self.items: List[Any] = [] + self.span = span + self.transform_fn = transform_fn + + def __aiter__(self) -> "_ContextPreservedAsyncGeneratorWrapper": + return self + + async def __anext__(self) -> Any: try: - async for item in generator: - items.append(item) + # Run the generator's __anext__ in the preserved context + try: + # Python 3.10+ approach with context parameter + item = await asyncio.create_task( + self.generator.__anext__(), # type: ignore + context=self.context, + ) # type: ignore + except TypeError: + # Python < 3.10 fallback - context parameter not supported + item = await self.generator.__anext__() - yield item + self.items.append(item) - finally: - output: Any = items + return item - if transform_to_string is not None: - output = transform_to_string(items) + except StopAsyncIteration: + # Handle output and span cleanup when generator is exhausted + output: Any = self.items - elif all(isinstance(item, str) for item in items): - output = "".join(items) + if self.transform_fn is not None: + output = self.transform_fn(self.items) - langfuse_span_or_generation.update(output=output) - langfuse_span_or_generation.end() + elif all(isinstance(item, str) for item in self.items): + output = "".join(self.items) + self.span.update(output=output).end() -_decorator = LangfuseDecorator() + raise # Re-raise StopAsyncIteration + except Exception as e: + self.span.update(level="ERROR", status_message=str(e)).end() -observe = _decorator.observe + raise diff --git a/tests/test_decorators.py b/tests/test_decorators.py index fe0a7f4c3..5803d531b 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,5 +1,6 @@ import asyncio import os +import sys from collections import defaultdict from concurrent.futures import ThreadPoolExecutor from time import sleep @@ -8,6 +9,7 @@ import pytest from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI +from opentelemetry import trace from langfuse import Langfuse, get_client, observe from langfuse._client.environment_variables import LANGFUSE_PUBLIC_KEY @@ -1686,3 +1688,282 @@ async def async_root_function(*args, **kwargs): # Reset instances to not leak to other test suites removeMockResourceManagerInstances() + + +def test_sync_generator_context_preservation(): + """Test that sync generators preserve context when consumed later (e.g., by streaming responses)""" + langfuse = get_client() + mock_trace_id = langfuse.create_trace_id() + + # Global variable to capture span information + span_info = {} + + @observe(name="sync_generator") + def create_generator(): + current_span = trace.get_current_span() + span_info["generator_span_id"] = trace.format_span_id( + current_span.get_span_context().span_id + ) + + for i in range(3): + yield f"item_{i}" + + @observe(name="root") + def root_function(): + current_span = trace.get_current_span() + span_info["root_span_id"] = trace.format_span_id( + current_span.get_span_context().span_id + ) + + # Return generator without consuming it (like FastAPI StreamingResponse would) + return create_generator() + + # Simulate the scenario where generator is consumed after root function exits + generator = root_function(langfuse_trace_id=mock_trace_id) + + # Consume generator later (like FastAPI would) + items = list(generator) + + langfuse.flush() + + # Verify results + assert items == ["item_0", "item_1", "item_2"] + assert ( + span_info["generator_span_id"] != "0000000000000000" + ), "Generator context should be preserved" + assert ( + span_info["root_span_id"] != span_info["generator_span_id"] + ), "Should have different span IDs" + + # Verify trace structure + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + + # Verify both observations are present + observation_names = [obs.name for obs in trace_data.observations] + assert "root" in observation_names + assert "sync_generator" in observation_names + + # Verify generator observation has output + generator_obs = next( + obs for obs in trace_data.observations if obs.name == "sync_generator" + ) + assert generator_obs.output == "item_0item_1item_2" + + +@pytest.mark.asyncio +@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") +async def test_async_generator_context_preservation(): + """Test that async generators preserve context when consumed later (e.g., by streaming responses)""" + langfuse = get_client() + mock_trace_id = langfuse.create_trace_id() + + # Global variable to capture span information + span_info = {} + + @observe(name="async_generator") + async def create_async_generator(): + current_span = trace.get_current_span() + span_info["generator_span_id"] = trace.format_span_id( + current_span.get_span_context().span_id + ) + + for i in range(3): + await asyncio.sleep(0.001) # Simulate async work + yield f"async_item_{i}" + + @observe(name="root") + async def root_function(): + current_span = trace.get_current_span() + span_info["root_span_id"] = trace.format_span_id( + current_span.get_span_context().span_id + ) + + # Return generator without consuming it (like FastAPI StreamingResponse would) + return create_async_generator() + + # Simulate the scenario where generator is consumed after root function exits + generator = await root_function(langfuse_trace_id=mock_trace_id) + + # Consume generator later (like FastAPI would) + items = [] + async for item in generator: + items.append(item) + + langfuse.flush() + + # Verify results + assert items == ["async_item_0", "async_item_1", "async_item_2"] + assert ( + span_info["generator_span_id"] != "0000000000000000" + ), "Generator context should be preserved" + assert ( + span_info["root_span_id"] != span_info["generator_span_id"] + ), "Should have different span IDs" + + # Verify trace structure + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + + # Verify both observations are present + observation_names = [obs.name for obs in trace_data.observations if obs.name] + assert "root" in observation_names + assert "async_generator" in observation_names + + # Verify generator observation has output + generator_obs = next( + obs for obs in trace_data.observations if obs.name == "async_generator" + ) + assert generator_obs.output == "async_item_0async_item_1async_item_2" + + +@pytest.mark.asyncio +@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") +async def test_async_generator_context_preservation_with_trace_hierarchy(): + """Test that async generators maintain proper parent-child span relationships""" + langfuse = get_client() + mock_trace_id = langfuse.create_trace_id() + + # Global variables to capture span information + span_info = {} + + @observe(name="child_stream") + async def child_generator(): + current_span = trace.get_current_span() + span_context = current_span.get_span_context() + span_info["child_span_id"] = trace.format_span_id(span_context.span_id) + span_info["child_trace_id"] = trace.format_trace_id(span_context.trace_id) + + for i in range(2): + await asyncio.sleep(0.001) + yield f"child_{i}" + + @observe(name="parent_root") + async def parent_function(): + current_span = trace.get_current_span() + span_context = current_span.get_span_context() + span_info["parent_span_id"] = trace.format_span_id(span_context.span_id) + span_info["parent_trace_id"] = trace.format_trace_id(span_context.trace_id) + + # Create and return child generator + return child_generator() + + # Execute parent function + generator = await parent_function(langfuse_trace_id=mock_trace_id) + + # Consume generator (simulating delayed consumption) + items = [item async for item in generator] + + langfuse.flush() + + # Verify results + assert items == ["child_0", "child_1"] + + # Verify span hierarchy + assert ( + span_info["parent_span_id"] != span_info["child_span_id"] + ), "Parent and child should have different span IDs" + assert ( + span_info["parent_trace_id"] == span_info["child_trace_id"] + ), "Parent and child should share same trace ID" + assert ( + span_info["child_span_id"] != "0000000000000000" + ), "Child context should be preserved" + + # Verify trace structure + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + + # Check both observations exist + observation_names = [obs.name for obs in trace_data.observations if obs.name] + assert "parent_root" in observation_names + assert "child_stream" in observation_names + + +@pytest.mark.asyncio +@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") +async def test_async_generator_exception_handling_with_context(): + """Test that exceptions in async generators are properly handled while preserving context""" + langfuse = get_client() + mock_trace_id = langfuse.create_trace_id() + + @observe(name="failing_generator") + async def failing_generator(): + current_span = trace.get_current_span() + # Verify we have valid context even when exception occurs + assert ( + trace.format_span_id(current_span.get_span_context().span_id) + != "0000000000000000" + ) + + yield "first_item" + await asyncio.sleep(0.001) + raise ValueError("Generator failure test") + yield "never_reached" # This should never execute + + @observe(name="root") + async def root_function(): + return failing_generator() + + # Execute and consume generator + generator = await root_function(langfuse_trace_id=mock_trace_id) + + items = [] + with pytest.raises(ValueError, match="Generator failure test"): + async for item in generator: + items.append(item) + + langfuse.flush() + + # Verify partial results + assert items == ["first_item"] + + # Verify trace structure - should have both observations despite exception + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + + # Check that the failing generator observation has ERROR level + failing_obs = next( + obs for obs in trace_data.observations if obs.name == "failing_generator" + ) + assert failing_obs.level == "ERROR" + assert "Generator failure test" in failing_obs.status_message + + +def test_sync_generator_empty_context_preservation(): + """Test that empty sync generators work correctly with context preservation""" + langfuse = get_client() + mock_trace_id = langfuse.create_trace_id() + + @observe(name="empty_generator") + def empty_generator(): + current_span = trace.get_current_span() + # Should have valid context even for empty generator + assert ( + trace.format_span_id(current_span.get_span_context().span_id) + != "0000000000000000" + ) + return + yield # Unreachable + + @observe(name="root") + def root_function(): + return empty_generator() + + generator = root_function(langfuse_trace_id=mock_trace_id) + items = list(generator) + + langfuse.flush() + + # Verify results + assert items == [] + + # Verify trace structure + trace_data = get_api().trace.get(mock_trace_id) + assert len(trace_data.observations) == 2 + + # Verify empty generator observation + empty_obs = next( + obs for obs in trace_data.observations if obs.name == "empty_generator" + ) + assert empty_obs.output is None From 140d192f89071f68d56cd4597825af6accc454ed Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:14:35 +0200 Subject: [PATCH 068/296] chore: release v3.5.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 74415ba37..0932c7668 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.5.1" +__version__ = "3.5.2" diff --git a/pyproject.toml b/pyproject.toml index bfa63ced0..3b7ff8ae4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.5.1" +version = "3.5.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 60748e801268b3fcbf8d350da2f137fc5d91fb37 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 29 Sep 2025 16:42:53 +0200 Subject: [PATCH 069/296] feat(api): update API spec from langfuse/langfuse 9b8868a (#1384) --- langfuse/api/__init__.py | 2 + langfuse/api/reference.md | 85 ++++++++++ langfuse/api/resources/__init__.py | 7 +- .../api/resources/score_configs/__init__.py | 4 +- .../api/resources/score_configs/client.py | 159 ++++++++++++++++++ .../resources/score_configs/types/__init__.py | 3 +- .../types/update_score_config_request.py | 81 +++++++++ 7 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 langfuse/api/resources/score_configs/types/update_score_config_request.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 932a60e93..3dd6de4e4 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -200,6 +200,7 @@ UpdateGenerationBody, UpdateGenerationEvent, UpdateObservationEvent, + UpdateScoreConfigRequest, UpdateSpanBody, UpdateSpanEvent, UpsertLlmConnectionRequest, @@ -433,6 +434,7 @@ "UpdateGenerationBody", "UpdateGenerationEvent", "UpdateObservationEvent", + "UpdateScoreConfigRequest", "UpdateSpanBody", "UpdateSpanEvent", "UpsertLlmConnectionRequest", diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 6b243980f..b9e598e7e 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -5904,6 +5904,91 @@ client.score_configs.get_by_id(
+ + + + +
client.score_configs.update(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Update a score config +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import UpdateScoreConfigRequest +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.score_configs.update( + config_id="configId", + request=UpdateScoreConfigRequest(), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**config_id:** `str` — The unique langfuse identifier of a score config + +
+
+ +
+
+ +**request:** `UpdateScoreConfigRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index 8b0f6ec76..ae77cad9d 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -234,7 +234,11 @@ UserMeta, ) from .score import CreateScoreRequest, CreateScoreResponse -from .score_configs import CreateScoreConfigRequest, ScoreConfigs +from .score_configs import ( + CreateScoreConfigRequest, + ScoreConfigs, + UpdateScoreConfigRequest, +) from .score_v_2 import ( GetScoresResponse, GetScoresResponseData, @@ -449,6 +453,7 @@ "UpdateGenerationBody", "UpdateGenerationEvent", "UpdateObservationEvent", + "UpdateScoreConfigRequest", "UpdateSpanBody", "UpdateSpanEvent", "UpsertLlmConnectionRequest", diff --git a/langfuse/api/resources/score_configs/__init__.py b/langfuse/api/resources/score_configs/__init__.py index e46e2a578..da401d35d 100644 --- a/langfuse/api/resources/score_configs/__init__.py +++ b/langfuse/api/resources/score_configs/__init__.py @@ -1,5 +1,5 @@ # This file was auto-generated by Fern from our API Definition. -from .types import CreateScoreConfigRequest, ScoreConfigs +from .types import CreateScoreConfigRequest, ScoreConfigs, UpdateScoreConfigRequest -__all__ = ["CreateScoreConfigRequest", "ScoreConfigs"] +__all__ = ["CreateScoreConfigRequest", "ScoreConfigs", "UpdateScoreConfigRequest"] diff --git a/langfuse/api/resources/score_configs/client.py b/langfuse/api/resources/score_configs/client.py index 7bd2a72df..7faea8312 100644 --- a/langfuse/api/resources/score_configs/client.py +++ b/langfuse/api/resources/score_configs/client.py @@ -16,6 +16,7 @@ from ..commons.types.score_config import ScoreConfig from .types.create_score_config_request import CreateScoreConfigRequest from .types.score_configs import ScoreConfigs +from .types.update_score_config_request import UpdateScoreConfigRequest # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -234,6 +235,81 @@ def get_by_id( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def update( + self, + config_id: str, + *, + request: UpdateScoreConfigRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfig: + """ + Update a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + request : UpdateScoreConfigRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + from langfuse import UpdateScoreConfigRequest + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_configs.update( + config_id="configId", + request=UpdateScoreConfigRequest(), + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/score-configs/{jsonable_encoder(config_id)}", + method="PATCH", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + class AsyncScoreConfigsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -471,3 +547,86 @@ async def main() -> None: except JSONDecodeError: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + + async def update( + self, + config_id: str, + *, + request: UpdateScoreConfigRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfig: + """ + Update a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + request : UpdateScoreConfigRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + import asyncio + + from langfuse import UpdateScoreConfigRequest + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_configs.update( + config_id="configId", + request=UpdateScoreConfigRequest(), + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/score-configs/{jsonable_encoder(config_id)}", + method="PATCH", + json=request, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/score_configs/types/__init__.py b/langfuse/api/resources/score_configs/types/__init__.py index 401650f2b..1c328b614 100644 --- a/langfuse/api/resources/score_configs/types/__init__.py +++ b/langfuse/api/resources/score_configs/types/__init__.py @@ -2,5 +2,6 @@ from .create_score_config_request import CreateScoreConfigRequest from .score_configs import ScoreConfigs +from .update_score_config_request import UpdateScoreConfigRequest -__all__ = ["CreateScoreConfigRequest", "ScoreConfigs"] +__all__ = ["CreateScoreConfigRequest", "ScoreConfigs", "UpdateScoreConfigRequest"] diff --git a/langfuse/api/resources/score_configs/types/update_score_config_request.py b/langfuse/api/resources/score_configs/types/update_score_config_request.py new file mode 100644 index 000000000..ce5f980b8 --- /dev/null +++ b/langfuse/api/resources/score_configs/types/update_score_config_request.py @@ -0,0 +1,81 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from ...commons.types.config_category import ConfigCategory + + +class UpdateScoreConfigRequest(pydantic_v1.BaseModel): + is_archived: typing.Optional[bool] = pydantic_v1.Field( + alias="isArchived", default=None + ) + """ + The status of the score config showing if it is archived or not + """ + + name: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + The name of the score config + """ + + categories: typing.Optional[typing.List[ConfigCategory]] = pydantic_v1.Field( + default=None + ) + """ + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + """ + + min_value: typing.Optional[float] = pydantic_v1.Field( + alias="minValue", default=None + ) + """ + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + """ + + max_value: typing.Optional[float] = pydantic_v1.Field( + alias="maxValue", default=None + ) + """ + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + """ + + description: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} From 64e812f5e3c020e35a5fc3b5ae4ba2f0a17c04b6 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:34:16 +0200 Subject: [PATCH 070/296] fix(client): setting OTEL span status as error on Langfuse error (#1387) (#1388) Co-authored-by: Yashwanth <53632453+yash025@users.noreply.github.com> --- langfuse/_client/span.py | 27 +++++ tests/test_otel.py | 226 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 9fa9c7489..c078d995d 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -31,6 +31,7 @@ from opentelemetry import trace as otel_trace_api from opentelemetry.util._decorator import _AgnosticContextManager +from opentelemetry.trace.status import Status, StatusCode from langfuse.model import PromptClient @@ -188,6 +189,8 @@ def __init__( self._otel_span.set_attributes( {k: v for k, v in attributes.items() if v is not None} ) + # Set OTEL span status if level is ERROR + self._set_otel_span_status_if_error(level=level, status_message=status_message) def end(self, *, end_time: Optional[int] = None) -> "LangfuseObservationWrapper": """End the span, marking it as completed. @@ -540,6 +543,28 @@ def _process_media_in_attribute( return data + def _set_otel_span_status_if_error( + self, *, level: Optional[SpanLevel] = None, status_message: Optional[str] = None + ) -> None: + """Set OpenTelemetry span status to ERROR if level is ERROR. + + This method sets the underlying OpenTelemetry span status to ERROR when the + Langfuse observation level is set to ERROR, ensuring consistency between + Langfuse and OpenTelemetry error states. + + Args: + level: The span level to check + status_message: Optional status message to include as description + """ + if level == "ERROR" and self._otel_span.is_recording(): + try: + self._otel_span.set_status( + Status(StatusCode.ERROR, description=status_message) + ) + except Exception: + # Silently ignore any errors when setting OTEL status to avoid existing flow disruptions + pass + def update( self, *, @@ -636,6 +661,8 @@ def update( ) self._otel_span.set_attributes(attributes=attributes) + # Set OTEL span status if level is ERROR + self._set_otel_span_status_if_error(level=level, status_message=status_message) return self diff --git a/tests/test_otel.py b/tests/test_otel.py index fd29ce671..623e866b5 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -944,6 +944,232 @@ def test_error_handling(self, langfuse_client, memory_exporter): == "Test error message" ) + def test_error_level_in_span_creation(self, langfuse_client, memory_exporter): + """Test that OTEL span status is set to ERROR when creating spans with level='ERROR'.""" + # Create a span with level="ERROR" at creation time + span = langfuse_client.start_span( + name="create-error-span", + level="ERROR", + status_message="Initial error state" + ) + span.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == "create-error-span" + ] + assert len(raw_spans) == 1, "Expected one span" + raw_span = raw_spans[0] + + # Verify OTEL span status was set to ERROR + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR + assert raw_span.status.description == "Initial error state" + + # Also verify Langfuse attributes + spans = self.get_spans_by_name(memory_exporter, "create-error-span") + span_data = spans[0] + attributes = span_data["attributes"] + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Initial error state" + + def test_error_level_in_span_update(self, langfuse_client, memory_exporter): + """Test that OTEL span status is set to ERROR when updating spans to level='ERROR'.""" + # Create a normal span + span = langfuse_client.start_span(name="update-error-span", level="INFO") + + # Update it to ERROR level + span.update(level="ERROR", status_message="Updated to error state") + span.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == "update-error-span" + ] + assert len(raw_spans) == 1, "Expected one span" + raw_span = raw_spans[0] + + # Verify OTEL span status was set to ERROR + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR + assert raw_span.status.description == "Updated to error state" + + # Also verify Langfuse attributes + spans = self.get_spans_by_name(memory_exporter, "update-error-span") + span_data = spans[0] + attributes = span_data["attributes"] + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Updated to error state" + + def test_generation_error_level_in_creation(self, langfuse_client, memory_exporter): + """Test that OTEL span status is set to ERROR when creating generations with level='ERROR'.""" + # Create a generation with level="ERROR" at creation time + generation = langfuse_client.start_generation( + name="create-error-generation", + model="gpt-4", + level="ERROR", + status_message="Generation failed during creation" + ) + generation.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == "create-error-generation" + ] + assert len(raw_spans) == 1, "Expected one span" + raw_span = raw_spans[0] + + # Verify OTEL span status was set to ERROR + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR + assert raw_span.status.description == "Generation failed during creation" + + # Also verify Langfuse attributes + spans = self.get_spans_by_name(memory_exporter, "create-error-generation") + span_data = spans[0] + attributes = span_data["attributes"] + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Generation failed during creation" + + def test_generation_error_level_in_update(self, langfuse_client, memory_exporter): + """Test that OTEL span status is set to ERROR when updating generations to level='ERROR'.""" + # Create a normal generation + generation = langfuse_client.start_generation( + name="update-error-generation", + model="gpt-4", + level="INFO" + ) + + # Update it to ERROR level + generation.update(level="ERROR", status_message="Generation failed during execution") + generation.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == "update-error-generation" + ] + assert len(raw_spans) == 1, "Expected one span" + raw_span = raw_spans[0] + + # Verify OTEL span status was set to ERROR + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR + assert raw_span.status.description == "Generation failed during execution" + + # Also verify Langfuse attributes + spans = self.get_spans_by_name(memory_exporter, "update-error-generation") + span_data = spans[0] + attributes = span_data["attributes"] + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Generation failed during execution" + + def test_non_error_levels_dont_set_otel_status(self, langfuse_client, memory_exporter): + """Test that non-ERROR levels don't set OTEL span status to ERROR.""" + # Test different non-error levels + test_levels = ["INFO", "WARNING", "DEBUG", None] + + for i, level in enumerate(test_levels): + span_name = f"non-error-span-{i}" + span = langfuse_client.start_span(name=span_name, level=level) + + # Update with same level to test update path too + if level is not None: + span.update(level=level, status_message="Not an error") + + span.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == span_name + ] + assert len(raw_spans) == 1, f"Expected one span for {span_name}" + raw_span = raw_spans[0] + + # Verify OTEL span status was NOT set to ERROR + from opentelemetry.trace.status import StatusCode + # Default status should be UNSET, not ERROR + assert raw_span.status.status_code != StatusCode.ERROR, f"Level {level} should not set ERROR status" + + def test_multiple_error_updates(self, langfuse_client, memory_exporter): + """Test that multiple ERROR level updates work correctly.""" + # Create a span + span = langfuse_client.start_span(name="multi-error-span") + + # First error update + span.update(level="ERROR", status_message="First error") + + # Second error update - should overwrite the first + span.update(level="ERROR", status_message="Second error") + + span.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == "multi-error-span" + ] + assert len(raw_spans) == 1, "Expected one span" + raw_span = raw_spans[0] + + # Verify OTEL span status shows the last error message + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR + assert raw_span.status.description == "Second error" + + def test_error_without_status_message(self, langfuse_client, memory_exporter): + """Test that ERROR level works even without status_message.""" + # Create a span with ERROR level but no status message + span = langfuse_client.start_span(name="error-no-message-span", level="ERROR") + span.end() + + # Get the raw OTEL spans to check the status + raw_spans = [ + s for s in memory_exporter.get_finished_spans() + if s.name == "error-no-message-span" + ] + assert len(raw_spans) == 1, "Expected one span" + raw_span = raw_spans[0] + + # Verify OTEL span status was set to ERROR even without description + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR + # Description should be None when no status_message provided + assert raw_span.status.description is None + + def test_different_observation_types_error_handling(self, langfuse_client, memory_exporter): + """Test that ERROR level setting works for different observation types.""" + # Test different observation types + observation_types = ["agent", "tool", "chain", "retriever", "evaluator", "embedding", "guardrail"] + + # Create a parent span for child observations + with langfuse_client.start_as_current_span(name="error-test-parent") as parent: + for obs_type in observation_types: + # Create observation with ERROR level + obs = parent.start_observation( + name=f"error-{obs_type}", + as_type=obs_type, + level="ERROR", + status_message=f"{obs_type} failed" + ) + obs.end() + + # Check that all observations have correct OTEL status + raw_spans = memory_exporter.get_finished_spans() + + for obs_type in observation_types: + obs_spans = [s for s in raw_spans if s.name == f"error-{obs_type}"] + assert len(obs_spans) == 1, f"Expected one span for {obs_type}" + + raw_span = obs_spans[0] + from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR, f"{obs_type} should have ERROR status" + assert raw_span.status.description == f"{obs_type} failed", f"{obs_type} should have correct description" + class TestAdvancedSpans(TestOTelBase): """Tests for advanced span functionality including generations, timing, and usage metrics.""" From 06912ce1581e9aedc5ae5951310d1c1469090808 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:01:09 +0200 Subject: [PATCH 071/296] chore: release v3.6.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 0932c7668..761dfd690 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.5.2" +__version__ = "3.6.0" diff --git a/pyproject.toml b/pyproject.toml index 3b7ff8ae4..61f245bb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.5.2" +version = "3.6.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 0dc0d2c10ca27118b631b82c84954a03a0f30c64 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:32:42 +0200 Subject: [PATCH 072/296] fix(experiments): pass full evaluation to score creation (#1391) --- langfuse/_client/client.py | 5 +- tests/test_experiments.py | 129 +++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ebc65e988..d0f946547 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2730,6 +2730,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult: comment=evaluation.comment, metadata=evaluation.metadata, data_type=evaluation.data_type, # type: ignore + config_id=evaluation.config_id, ) except Exception as e: @@ -2856,9 +2857,11 @@ async def _process_experiment_item( self.create_score( trace_id=trace_id, name=evaluation.name, - value=evaluation.value or -1, + value=evaluation.value, # type: ignore comment=evaluation.comment, metadata=evaluation.metadata, + config_id=evaluation.config_id, + data_type=evaluation.data_type, # type: ignore ) except Exception as e: diff --git a/tests/test_experiments.py b/tests/test_experiments.py index 168310970..db3d74a65 100644 --- a/tests/test_experiments.py +++ b/tests/test_experiments.py @@ -668,3 +668,132 @@ def test_format_experiment_results_basic(): langfuse_client.flush() time.sleep(1) + + +def test_boolean_score_types(): + """Test that BOOLEAN score types are properly ingested and persisted.""" + from langfuse.api import ScoreDataType + + langfuse_client = get_client() + + def boolean_evaluator(*, input, output, expected_output=None, **kwargs): + """Boolean evaluator that checks if output contains the expected answer.""" + if not expected_output: + return Evaluation( + name="has_expected_content", + value=False, + data_type=ScoreDataType.BOOLEAN, + comment="No expected output to check", + ) + + contains_expected = expected_output.lower() in str(output).lower() + return Evaluation( + name="has_expected_content", + value=contains_expected, + data_type=ScoreDataType.BOOLEAN, + comment=f"Output {'contains' if contains_expected else 'does not contain'} expected content", + ) + + def boolean_run_evaluator(*, item_results: List[ExperimentItemResult], **kwargs): + """Run evaluator that returns boolean based on all items passing.""" + if not item_results: + return Evaluation( + name="all_items_pass", + value=False, + data_type=ScoreDataType.BOOLEAN, + comment="No items to evaluate", + ) + + # Check if all boolean evaluations are True + all_pass = True + for item_result in item_results: + for evaluation in item_result.evaluations: + if ( + evaluation.name == "has_expected_content" + and evaluation.value is False + ): + all_pass = False + break + if not all_pass: + break + + return Evaluation( + name="all_items_pass", + value=all_pass, + data_type=ScoreDataType.BOOLEAN, + comment=f"{'All' if all_pass else 'Not all'} items passed the boolean evaluation", + ) + + # Test data where some items should pass and some should fail + test_data = [ + {"input": "What is the capital of Germany?", "expected_output": "Berlin"}, + {"input": "What is the capital of France?", "expected_output": "Paris"}, + {"input": "What is the capital of Spain?", "expected_output": "Madrid"}, + ] + + # Task that returns correct answers for Germany and France, but wrong for Spain + def mock_task_with_boolean_results(*, item: ExperimentItem, **kwargs): + input_val = ( + item.get("input") + if isinstance(item, dict) + else getattr(item, "input", "unknown") + ) + input_str = str(input_val) if input_val is not None else "" + + if "Germany" in input_str: + return "The capital is Berlin" + elif "France" in input_str: + return "The capital is Paris" + else: + return "I don't know the capital" + + result = langfuse_client.run_experiment( + name="Boolean score type test", + description="Test BOOLEAN data type in scores", + data=test_data, + task=mock_task_with_boolean_results, + evaluators=[boolean_evaluator], + run_evaluators=[boolean_run_evaluator], + ) + + # Validate basic result structure + assert len(result.item_results) == 3 + assert len(result.run_evaluations) == 1 + + # Validate individual item evaluations have boolean values + expected_results = [ + True, + True, + False, + ] # Germany and France should pass, Spain should fail + for i, item_result in enumerate(result.item_results): + assert len(item_result.evaluations) == 1 + eval_result = item_result.evaluations[0] + assert eval_result.name == "has_expected_content" + assert isinstance(eval_result.value, bool) + assert eval_result.value == expected_results[i] + assert eval_result.data_type == ScoreDataType.BOOLEAN + + # Validate run evaluation is boolean and should be False (not all items passed) + run_eval = result.run_evaluations[0] + assert run_eval.name == "all_items_pass" + assert isinstance(run_eval.value, bool) + assert run_eval.value is False # Spain should fail, so not all pass + assert run_eval.data_type == ScoreDataType.BOOLEAN + + # Flush and wait for server processing + langfuse_client.flush() + time.sleep(3) + + # Verify scores are persisted via API with correct data types + api = get_api() + for i, item_result in enumerate(result.item_results): + trace_id = item_result.trace_id + assert trace_id is not None, f"Item {i} should have a trace_id" + + # Fetch trace from API to verify score persistence + trace = api.trace.get(trace_id) + assert trace is not None, f"Trace {trace_id} should exist" + + for score in trace.scores: + assert score.data_type == "BOOLEAN" From 9b4dcd5a8b4d9600a8993412699c40dd57bb76b7 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:33:10 +0200 Subject: [PATCH 073/296] chore: release v3.6.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 761dfd690..07de736e5 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.6.0" +__version__ = "3.6.1" diff --git a/pyproject.toml b/pyproject.toml index 61f245bb1..748670e89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.6.0" +version = "3.6.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 2470af7eb5a653da05f875dbe111637cc3d4935c Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 8 Oct 2025 11:26:14 +0200 Subject: [PATCH 074/296] feat(api): update API spec from langfuse/langfuse dee644d (#1398) --- langfuse/api/reference.md | 32 +++++++++++++++ langfuse/api/resources/trace/client.py | 54 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index b9e598e7e..f2e9baa07 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -6906,6 +6906,38 @@ client.trace.list()
+**filter:** `typing.Optional[str]` + +JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). +Each filter condition has the following structure: +```json +[ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } +] +``` + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
diff --git a/langfuse/api/resources/trace/client.py b/langfuse/api/resources/trace/client.py index 824142a27..7c5d803d7 100644 --- a/langfuse/api/resources/trace/client.py +++ b/langfuse/api/resources/trace/client.py @@ -173,6 +173,7 @@ def list( release: typing.Optional[str] = None, environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Traces: """ @@ -216,6 +217,31 @@ def list( fields : typing.Optional[str] Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -258,6 +284,7 @@ def list( "release": release, "environment": environment, "fields": fields, + "filter": filter, }, request_options=request_options, ) @@ -524,6 +551,7 @@ async def list( release: typing.Optional[str] = None, environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Traces: """ @@ -567,6 +595,31 @@ async def list( fields : typing.Optional[str] Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -617,6 +670,7 @@ async def main() -> None: "release": release, "environment": environment, "fields": fields, + "filter": filter, }, request_options=request_options, ) From 746c951c86d63057f95d45e55b8fe23082cbbeb6 Mon Sep 17 00:00:00 2001 From: abhishar432 Date: Fri, 10 Oct 2025 13:23:37 +0530 Subject: [PATCH 075/296] fix(resource-manager): ensure ingestion and media queues are always flushed (#1401) --- langfuse/_client/resource_manager.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 70ed5b17c..28c24e919 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -398,11 +398,9 @@ def _stop_and_join_consumer_threads(self) -> None: def flush(self) -> None: tracer_provider = cast(TracerProvider, otel_trace_api.get_tracer_provider()) - if isinstance(tracer_provider, otel_trace_api.ProxyTracerProvider): - return - - tracer_provider.force_flush() - langfuse_logger.debug("Successfully flushed OTEL tracer provider") + if not isinstance(tracer_provider, otel_trace_api.ProxyTracerProvider): + tracer_provider.force_flush() + langfuse_logger.debug("Successfully flushed OTEL tracer provider") self._score_ingestion_queue.join() langfuse_logger.debug("Successfully flushed score ingestion queue") @@ -415,10 +413,8 @@ def shutdown(self) -> None: atexit.unregister(self.shutdown) tracer_provider = cast(TracerProvider, otel_trace_api.get_tracer_provider()) - if isinstance(tracer_provider, otel_trace_api.ProxyTracerProvider): - return - - tracer_provider.force_flush() + if not isinstance(tracer_provider, otel_trace_api.ProxyTracerProvider): + tracer_provider.force_flush() self._stop_and_join_consumer_threads() From fa290c4d41f15c4597380e1a8f7d6a2cc7e18382 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:07:45 +0200 Subject: [PATCH 076/296] chore: release v3.6.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 07de736e5..29065374e 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.6.1" +__version__ = "3.6.2" diff --git a/pyproject.toml b/pyproject.toml index 748670e89..7cbe1ad03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.6.1" +version = "3.6.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 496aca36aed1505d8a85a4629918019aade9724b Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 13 Oct 2025 11:30:53 +0200 Subject: [PATCH 077/296] feat(api): update API spec from langfuse/langfuse fa2979b (#1403) Co-authored-by: langfuse-bot --- langfuse/api/resources/commons/types/base_score.py | 2 +- langfuse/api/resources/commons/types/base_score_v_1.py | 2 +- langfuse/api/resources/ingestion/types/score_body.py | 5 +++++ langfuse/api/resources/score/types/create_score_request.py | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/langfuse/api/resources/commons/types/base_score.py b/langfuse/api/resources/commons/types/base_score.py index 74e93d0bd..dd5449c83 100644 --- a/langfuse/api/resources/commons/types/base_score.py +++ b/langfuse/api/resources/commons/types/base_score.py @@ -37,7 +37,7 @@ class BaseScore(pydantic_v1.BaseModel): queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) """ - Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue. + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. """ environment: typing.Optional[str] = pydantic_v1.Field(default=None) diff --git a/langfuse/api/resources/commons/types/base_score_v_1.py b/langfuse/api/resources/commons/types/base_score_v_1.py index a89a4f7bb..478dcc6e6 100644 --- a/langfuse/api/resources/commons/types/base_score_v_1.py +++ b/langfuse/api/resources/commons/types/base_score_v_1.py @@ -31,7 +31,7 @@ class BaseScoreV1(pydantic_v1.BaseModel): queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) """ - Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue. + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. """ environment: typing.Optional[str] = pydantic_v1.Field(default=None) diff --git a/langfuse/api/resources/ingestion/types/score_body.py b/langfuse/api/resources/ingestion/types/score_body.py index 286c06514..549046564 100644 --- a/langfuse/api/resources/ingestion/types/score_body.py +++ b/langfuse/api/resources/ingestion/types/score_body.py @@ -35,6 +35,11 @@ class ScoreBody(pydantic_v1.BaseModel): ) name: str environment: typing.Optional[str] = None + queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) + """ + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + """ + value: CreateScoreValue = pydantic_v1.Field() """ The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) diff --git a/langfuse/api/resources/score/types/create_score_request.py b/langfuse/api/resources/score/types/create_score_request.py index d6ad037a4..36b0774ee 100644 --- a/langfuse/api/resources/score/types/create_score_request.py +++ b/langfuse/api/resources/score/types/create_score_request.py @@ -46,6 +46,11 @@ class CreateScoreRequest(pydantic_v1.BaseModel): The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. """ + queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) + """ + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + """ + data_type: typing.Optional[ScoreDataType] = pydantic_v1.Field( alias="dataType", default=None ) From dc09f509746c93b2b90c329b79198bfe0e3a121e Mon Sep 17 00:00:00 2001 From: Yorick Date: Thu, 16 Oct 2025 17:33:50 +0200 Subject: [PATCH 078/296] feat(observe): handle starlette.StreamingResponse (#1394) Starlette has a StreamingResponse type that wraps an AsyncGenerator, which is often used in fastapi http calls. By wrapping the result inside of it, we can get the correct timings and outputs for these generators in the resulting trace. --- langfuse/_client/observe.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index 6b9d52278..4338641b7 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -312,6 +312,16 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: transform_to_string, ) + # handle starlette.StreamingResponse + if type(result).__name__ == "StreamingResponse" and hasattr(result, "body_iterator"): + is_return_type_generator = True + + result.body_iterator = self._wrap_async_generator_result( + langfuse_span_or_generation, + result.body_iterator, + transform_to_string, + ) + langfuse_span_or_generation.update(output=result) return result @@ -416,6 +426,16 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: transform_to_string, ) + # handle starlette.StreamingResponse + if type(result).__name__ == "StreamingResponse" and hasattr(result, "body_iterator"): + is_return_type_generator = True + + result.body_iterator = self._wrap_async_generator_result( + langfuse_span_or_generation, + result.body_iterator, + transform_to_string, + ) + langfuse_span_or_generation.update(output=result) return result From cff0d9c9bd2be1b9a9251d20583b30f31981f27d Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:02:42 +0200 Subject: [PATCH 079/296] chore: release v3.7.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 29065374e..801fe542c 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.6.2" +__version__ = "3.7.0" diff --git a/pyproject.toml b/pyproject.toml index 7cbe1ad03..dad96000e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.6.2" +version = "3.7.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 1b61c36ced53a6010595488a7b22c05ef450b00c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:59:46 +0200 Subject: [PATCH 080/296] fix(client): HTTP headers concatenated by dashes instead of snakecase (#1404) --- langfuse/_client/span_processor.py | 6 +++--- langfuse/_utils/request.py | 6 +++--- tests/test_additional_headers_simple.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index baa72360c..369d5ff9e 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -83,9 +83,9 @@ def __init__( # Prepare default headers default_headers = { "Authorization": basic_auth_header, - "x_langfuse_sdk_name": "python", - "x_langfuse_sdk_version": langfuse_version, - "x_langfuse_public_key": public_key, + "x-langfuse-sdk-name": "python", + "x-langfuse-sdk-version": langfuse_version, + "x-langfuse-public-key": public_key, } # Merge additional headers if provided diff --git a/langfuse/_utils/request.py b/langfuse/_utils/request.py index b106cee2f..182fe3ffe 100644 --- a/langfuse/_utils/request.py +++ b/langfuse/_utils/request.py @@ -41,9 +41,9 @@ def generate_headers(self) -> dict: f"{self._public_key}:{self._secret_key}".encode("utf-8") ).decode("ascii"), "Content-Type": "application/json", - "x_langfuse_sdk_name": "python", - "x_langfuse_sdk_version": self._version, - "x_langfuse_public_key": self._public_key, + "x-langfuse-sdk-name": "python", + "x-langfuse-sdk-version": self._version, + "x-langfuse-public-key": self._public_key, } def batch_post(self, **kwargs: Any) -> httpx.Response: diff --git a/tests/test_additional_headers_simple.py b/tests/test_additional_headers_simple.py index 4298a3ba5..8a1d07134 100644 --- a/tests/test_additional_headers_simple.py +++ b/tests/test_additional_headers_simple.py @@ -156,8 +156,8 @@ def test_span_processor_has_additional_headers_in_otel_exporter(self): # Verify default headers are still present assert "Authorization" in exporter._headers - assert "x_langfuse_sdk_name" in exporter._headers - assert "x_langfuse_public_key" in exporter._headers + assert "x-langfuse-sdk-name" in exporter._headers + assert "x-langfuse-public-key" in exporter._headers # Check that our override worked assert exporter._headers["X-Override-Default"] == "override-value" @@ -179,5 +179,5 @@ def test_span_processor_none_additional_headers_works(self): # Verify default headers are present assert "Authorization" in exporter._headers - assert "x_langfuse_sdk_name" in exporter._headers - assert "x_langfuse_public_key" in exporter._headers + assert "x-langfuse-sdk-name" in exporter._headers + assert "x-langfuse-public-key" in exporter._headers From 671b2d36706fa85b97ed719826494eb0abf47418 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:44:28 +0200 Subject: [PATCH 081/296] feat(langchain): add v1 support (#1411) --- .github/workflows/ci.yml | 5 +- .pre-commit-config.yaml | 2 +- langfuse/_client/client.py | 4 +- langfuse/_utils/serializer.py | 2 +- langfuse/langchain/CallbackHandler.py | 85 +- poetry.lock | 2576 ++++++++++++------------- pyproject.toml | 10 +- tests/test_datasets.py | 2 +- tests/test_decorators.py | 2 +- tests/test_json.py | 5 +- tests/test_langchain.py | 445 +---- tests/test_langchain_integration.py | 4 +- tests/test_prompt_compilation.py | 2 +- 13 files changed, 1314 insertions(+), 1830 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e1ae37f7..1b8e0a83e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Install poetry uses: abatilo/actions-poetry@v2 - name: Setup a local virtual environment @@ -69,9 +69,10 @@ jobs: fail-fast: false matrix: python-version: - - "3.9" - "3.10" - "3.11" + - "3.12" + - "3.13" name: Test on Python version ${{ matrix.python-version }} steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45f88c352..59073b603 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: types_or: [python, pyi, jupyter] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.18.2 hooks: - id: mypy additional_dependencies: diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index d0f946547..09610567d 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -211,9 +211,7 @@ def __init__( additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, ): - self._host = host or cast( - str, os.environ.get(LANGFUSE_HOST, "https://cloud.langfuse.com") - ) + self._host = host or os.environ.get(LANGFUSE_HOST, "https://cloud.langfuse.com") self._environment = environment or cast( str, os.environ.get(LANGFUSE_TRACING_ENVIRONMENT) ) diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 289531b23..1350ea00c 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -19,7 +19,7 @@ # Attempt to import Serializable try: - from langchain.load.serializable import Serializable + from langchain_core.load.serializable import Serializable except ImportError: # If Serializable is not available, set it to a placeholder type class Serializable: # type: ignore diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 98ef24680..9e8278e28 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1,5 +1,17 @@ -import typing from contextvars import Token +from typing import ( + Any, + Dict, + List, + Literal, + Optional, + Sequence, + Set, + Type, + Union, + cast, +) +from uuid import UUID import pydantic from opentelemetry import context, trace @@ -16,41 +28,52 @@ LangfuseSpan, LangfuseTool, ) +from langfuse._utils import _get_timestamp +from langfuse.langchain.utils import _extract_model_name from langfuse.logger import langfuse_logger try: - import langchain # noqa - -except ImportError as e: - langfuse_logger.error( - f"Could not import langchain. The langchain integration will not work. {e}" - ) + import langchain -from typing import Any, Dict, List, Literal, Optional, Sequence, Set, Type, Union, cast -from uuid import UUID + if langchain.__version__.startswith("1"): + # Langchain v1 + from langchain_core.agents import AgentAction, AgentFinish + from langchain_core.callbacks import ( + BaseCallbackHandler as LangchainBaseCallbackHandler, + ) + from langchain_core.documents import Document + from langchain_core.messages import ( + AIMessage, + BaseMessage, + ChatMessage, + FunctionMessage, + HumanMessage, + SystemMessage, + ToolMessage, + ) + from langchain_core.outputs import ChatGeneration, LLMResult -from langfuse._utils import _get_timestamp -from langfuse.langchain.utils import _extract_model_name + else: + # Langchain v0 + from langchain.callbacks.base import ( # type: ignore + BaseCallbackHandler as LangchainBaseCallbackHandler, + ) + from langchain.schema.agent import AgentAction, AgentFinish # type: ignore + from langchain.schema.document import Document # type: ignore + from langchain_core.messages import ( + AIMessage, + BaseMessage, + ChatMessage, + FunctionMessage, + HumanMessage, + SystemMessage, + ToolMessage, + ) + from langchain_core.outputs import ( + ChatGeneration, + LLMResult, + ) -try: - from langchain.callbacks.base import ( - BaseCallbackHandler as LangchainBaseCallbackHandler, - ) - from langchain.schema.agent import AgentAction, AgentFinish - from langchain.schema.document import Document - from langchain_core.messages import ( - AIMessage, - BaseMessage, - ChatMessage, - FunctionMessage, - HumanMessage, - SystemMessage, - ToolMessage, - ) - from langchain_core.outputs import ( - ChatGeneration, - LLMResult, - ) except ImportError: raise ModuleNotFoundError( "Please install langchain to use the Langfuse langchain integration: 'pip install langchain'" @@ -1011,7 +1034,7 @@ def _flatten_comprehension(matrix: Any) -> Any: return [item for row in matrix for item in row] -def _parse_usage_model(usage: typing.Union[pydantic.BaseModel, dict]) -> Any: +def _parse_usage_model(usage: Union[pydantic.BaseModel, dict]) -> Any: # maintains a list of key translations. For each key, the usage model is checked # and a new object will be created with the new key if the key exists in the usage model # All non matched keys will remain on the object. diff --git a/poetry.lock b/poetry.lock index 4387fe601..51f812200 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -14,14 +13,13 @@ files = [ [[package]] name = "anyio" -version = "4.10.0" +version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, - {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, + {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, + {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, ] [package.dependencies] @@ -31,48 +29,25 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"langchain\" and python_version < \"3.11\"" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] +trio = ["trio (>=0.31.0)"] [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] - [[package]] name = "autoevals" version = "0.0.130" description = "Universal library for evaluating AI models" optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, @@ -96,7 +71,6 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -108,8 +82,6 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -117,14 +89,13 @@ files = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, ] [[package]] @@ -133,7 +104,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -141,91 +111,124 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.3" +version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, ] [[package]] @@ -234,7 +237,6 @@ version = "0.14.0" description = "Mustache templating language renderer" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, @@ -246,12 +248,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "extra == \"openai\" and platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -259,7 +259,6 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -271,12 +270,10 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -markers = {main = "extra == \"openai\""} [[package]] name = "exceptiongroup" @@ -284,8 +281,6 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -303,7 +298,6 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -314,14 +308,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.19.1" +version = "3.20.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, - {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, + {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, + {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, ] [[package]] @@ -330,7 +323,6 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -342,82 +334,12 @@ protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0)"] -[[package]] -name = "greenlet" -version = "3.2.4" -description = "Lightweight in-process concurrent programming" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"langchain\"" -files = [ - {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, - {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, - {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, - {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, - {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, - {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, - {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, - {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, - {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, - {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, - {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, - {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, - {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, - {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil", "setuptools"] - [[package]] name = "h11" version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -429,7 +351,6 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -451,7 +372,6 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -464,7 +384,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -472,14 +392,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" -version = "2.6.13" +version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, - {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, + {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, + {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, ] [package.extras] @@ -487,14 +406,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.10" +version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] +python-versions = ">=3.8" files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, ] [package.extras] @@ -506,7 +424,6 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -516,24 +433,23 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] @@ -542,7 +458,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -556,91 +471,114 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.10.0" +version = "0.11.1" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, - {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, - {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, - {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, - {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, - {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, - {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, - {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, - {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, - {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, - {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, - {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, - {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, - {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, - {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, - {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, - {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, - {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, - {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, -] -markers = {main = "extra == \"openai\""} +files = [ + {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, + {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, + {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87b2821795e28cc990939b68ce7a038edea680a24910bd68a79d54ff3f03c02"}, + {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83f6fa494d8bba14ab100417c80e70d32d737e805cb85be2052d771c76fcd1f8"}, + {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fbc6aea1daa2ec6f5ed465f0c5e7b0607175062ceebbea5ca70dd5ddab58083"}, + {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:302288e2edc43174bb2db838e94688d724f9aad26c5fb9a74f7a5fb427452a6a"}, + {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85db563fe3b367bb568af5d29dea4d4066d923b8e01f3417d25ebecd958de815"}, + {file = "jiter-0.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1c1ba2b6b22f775444ef53bc2d5778396d3520abc7b2e1da8eb0c27cb3ffb10"}, + {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:523be464b14f8fd0cc78da6964b87b5515a056427a2579f9085ce30197a1b54a"}, + {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25b99b3f04cd2a38fefb22e822e35eb203a2cd37d680dbbc0c0ba966918af336"}, + {file = "jiter-0.11.1-cp310-cp310-win32.whl", hash = "sha256:47a79e90545a596bb9104109777894033347b11180d4751a216afef14072dbe7"}, + {file = "jiter-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:cace75621ae9bd66878bf69fbd4dfc1a28ef8661e0c2d0eb72d3d6f1268eddf5"}, + {file = "jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2"}, + {file = "jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661"}, + {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8"}, + {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb"}, + {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1"}, + {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be"}, + {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4"}, + {file = "jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29"}, + {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9"}, + {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5"}, + {file = "jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a"}, + {file = "jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19"}, + {file = "jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869"}, + {file = "jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c"}, + {file = "jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d"}, + {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b"}, + {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4"}, + {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239"}, + {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711"}, + {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939"}, + {file = "jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54"}, + {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d"}, + {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250"}, + {file = "jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e"}, + {file = "jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87"}, + {file = "jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c"}, + {file = "jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663"}, + {file = "jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94"}, + {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00"}, + {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd"}, + {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14"}, + {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f"}, + {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96"}, + {file = "jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c"}, + {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646"}, + {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a"}, + {file = "jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b"}, + {file = "jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed"}, + {file = "jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d"}, + {file = "jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b"}, + {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58"}, + {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789"}, + {file = "jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec"}, + {file = "jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8"}, + {file = "jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676"}, + {file = "jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944"}, + {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9"}, + {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d"}, + {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee"}, + {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe"}, + {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90"}, + {file = "jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f"}, + {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a"}, + {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3"}, + {file = "jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea"}, + {file = "jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c"}, + {file = "jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991"}, + {file = "jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c"}, + {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8"}, + {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e"}, + {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f"}, + {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9"}, + {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08"}, + {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51"}, + {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437"}, + {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111"}, + {file = "jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7"}, + {file = "jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1"}, + {file = "jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe"}, + {file = "jiter-0.11.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:baa99c8db49467527658bb479857344daf0a14dff909b7f6714579ac439d1253"}, + {file = "jiter-0.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:860fe55fa3b01ad0edf2adde1098247ff5c303d0121f9ce028c03d4f88c69502"}, + {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:173dd349d99b6feaf5a25a6fbcaf3489a6f947708d808240587a23df711c67db"}, + {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14ac1dca837514cc946a6ac2c4995d9695303ecc754af70a3163d057d1a444ab"}, + {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69af47de5f93a231d5b85f7372d3284a5be8edb4cc758f006ec5a1406965ac5e"}, + {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:685f8b3abd3bbd3e06e4dfe2429ff87fd5d7a782701151af99b1fcbd80e31b2b"}, + {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d04afa2d4e5526e54ae8a58feea953b1844bf6e3526bc589f9de68e86d0ea01"}, + {file = "jiter-0.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e92b927259035b50d8e11a8fdfe0ebd014d883e4552d37881643fa289a4bcf1"}, + {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e7bd8be4fad8d4c5558b7801770cd2da6c072919c6f247cc5336edb143f25304"}, + {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:121381a77a3c85987f3eba0d30ceaca9116f7463bedeec2fa79b2e7286b89b60"}, + {file = "jiter-0.11.1-cp39-cp39-win32.whl", hash = "sha256:160225407f6dfabdf9be1b44e22f06bc293a78a28ffa4347054698bd712dad06"}, + {file = "jiter-0.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:028e0d59bcdfa1079f8df886cdaefc6f515c27a5288dec956999260c7e4a7cfd"}, + {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6"}, + {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad"}, + {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f"}, + {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa"}, + {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d"}, + {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838"}, + {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f"}, + {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960"}, + {file = "jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc"}, +] [[package]] name = "jsonpatch" @@ -648,12 +586,10 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] jsonpointer = ">=1.9" @@ -664,12 +600,10 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] -markers = {main = "extra == \"langchain\""} [[package]] name = "jsonschema" @@ -677,7 +611,6 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -699,7 +632,6 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -710,32 +642,23 @@ referencing = ">=0.31.0" [[package]] name = "langchain" -version = "0.3.27" +version = "1.0.1" description = "Building applications with LLMs through composability" -optional = true -python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "extra == \"langchain\"" +optional = false +python-versions = "<4.0.0,>=3.10.0" files = [ - {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, - {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, + {file = "langchain-1.0.1-py3-none-any.whl", hash = "sha256:48fd616413fee4843f12cad49e8b74ad6fc159640142ca885d03bd925cd24503"}, + {file = "langchain-1.0.1.tar.gz", hash = "sha256:8bf60e096ca9c06626d9161a46651405ef1d12b5863f7679283666f83f760dc5"}, ] [package.dependencies] -async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.3.72,<1.0.0" -langchain-text-splitters = ">=0.3.9,<1.0.0" -langsmith = ">=0.1.17" +langchain-core = ">=1.0.0,<2.0.0" +langgraph = ">=1.0.0,<1.1.0" pydantic = ">=2.7.4,<3.0.0" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" [package.extras] anthropic = ["langchain-anthropic"] aws = ["langchain-aws"] -azure-ai = ["langchain-azure-ai"] -cohere = ["langchain-cohere"] community = ["langchain-community"] deepseek = ["langchain-deepseek"] fireworks = ["langchain-fireworks"] @@ -752,89 +675,68 @@ xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "0.3.75" +version = "1.0.0" description = "Building applications with LLMs through composability" optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] +python-versions = "<4.0.0,>=3.10.0" files = [ - {file = "langchain_core-0.3.75-py3-none-any.whl", hash = "sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5"}, - {file = "langchain_core-0.3.75.tar.gz", hash = "sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed"}, + {file = "langchain_core-1.0.0-py3-none-any.whl", hash = "sha256:a94561bf75dd097c7d6e3864950f28dadc963f0bd810114de4095f41f634059b"}, + {file = "langchain_core-1.0.0.tar.gz", hash = "sha256:8e81c94a22fa3a362a0f101bbd1271bf3725e50cf1e31c84e8f4a1c731279785"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.3.45" -packaging = ">=23.2" -pydantic = ">=2.7.4" -PyYAML = ">=5.3" +jsonpatch = ">=1.33.0,<2.0.0" +langsmith = ">=0.3.45,<1.0.0" +packaging = ">=23.2.0,<26.0.0" +pydantic = ">=2.7.4,<3.0.0" +pyyaml = ">=5.3.0,<7.0.0" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" -typing-extensions = ">=4.7" +typing-extensions = ">=4.7.0,<5.0.0" [[package]] name = "langchain-openai" -version = "0.3.32" +version = "0.3.34" description = "An integration package connecting OpenAI and LangChain" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = "<4.0.0,>=3.9.0" files = [ - {file = "langchain_openai-0.3.32-py3-none-any.whl", hash = "sha256:3354f76822f7cc76d8069831fe2a77f9bc7ff3b4f13af788bd94e4c6e853b400"}, - {file = "langchain_openai-0.3.32.tar.gz", hash = "sha256:782ad669bd1bdb964456d8882c5178717adcfceecb482cc20005f770e43d346d"}, + {file = "langchain_openai-0.3.34-py3-none-any.whl", hash = "sha256:08d61d68a6d869c70d542171e149b9065668dedfc4fafcd4de8aeb5b933030a9"}, + {file = "langchain_openai-0.3.34.tar.gz", hash = "sha256:57916d462be5b8fd19e5cb2f00d4e5cf0465266a292d583de2fc693a55ba190e"}, ] [package.dependencies] -langchain-core = ">=0.3.74,<1.0.0" -openai = ">=1.99.9,<2.0.0" -tiktoken = ">=0.7,<1" - -[[package]] -name = "langchain-text-splitters" -version = "0.3.9" -description = "LangChain text splitting utilities" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"langchain\"" -files = [ - {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, - {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, -] - -[package.dependencies] -langchain-core = ">=0.3.72,<1.0.0" +langchain-core = ">=0.3.77,<2.0.0" +openai = ">=1.104.2,<3.0.0" +tiktoken = ">=0.7.0,<1.0.0" [[package]] name = "langgraph" -version = "0.6.7" +version = "1.0.0" description = "Building stateful, multi-actor applications with LLMs" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "langgraph-0.6.7-py3-none-any.whl", hash = "sha256:c724dd8c24806b70faf4903e8e20c0234f8c0a356e0e96a88035cbecca9df2cf"}, - {file = "langgraph-0.6.7.tar.gz", hash = "sha256:ba7fd17b8220142d6a4269b6038f2b3dcbcef42cd5ecf4a4c8d9b60b010830a6"}, + {file = "langgraph-1.0.0-py3-none-any.whl", hash = "sha256:4d478781832a1bc67e06c3eb571412ec47d7c57a5467d1f3775adf0e9dd4042c"}, + {file = "langgraph-1.0.0.tar.gz", hash = "sha256:5f83ed0e9bbcc37635bc49cbc9b3d9306605fa07504f955b7a871ed715f9964c"}, ] [package.dependencies] langchain-core = ">=0.1" langgraph-checkpoint = ">=2.1.0,<3.0.0" -langgraph-prebuilt = ">=0.6.0,<0.7.0" +langgraph-prebuilt = ">=1.0.0,<1.1.0" langgraph-sdk = ">=0.2.2,<0.3.0" pydantic = ">=2.7.4" xxhash = ">=3.5.0" [[package]] name = "langgraph-checkpoint" -version = "2.1.1" +version = "2.1.2" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "langgraph_checkpoint-2.1.1-py3-none-any.whl", hash = "sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7"}, - {file = "langgraph_checkpoint-2.1.1.tar.gz", hash = "sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d"}, + {file = "langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60"}, + {file = "langgraph_checkpoint-2.1.2.tar.gz", hash = "sha256:112e9d067a6eff8937caf198421b1ffba8d9207193f14ac6f89930c1260c06f9"}, ] [package.dependencies] @@ -843,14 +745,13 @@ ormsgpack = ">=1.10.0" [[package]] name = "langgraph-prebuilt" -version = "0.6.4" +version = "1.0.0" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "langgraph_prebuilt-0.6.4-py3-none-any.whl", hash = "sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344"}, - {file = "langgraph_prebuilt-0.6.4.tar.gz", hash = "sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f"}, + {file = "langgraph_prebuilt-1.0.0-py3-none-any.whl", hash = "sha256:ceaae4c5cee8c1f9b6468f76c114cafebb748aed0c93483b7c450e5a89de9c61"}, + {file = "langgraph_prebuilt-1.0.0.tar.gz", hash = "sha256:eb75dad9aca0137451ca0395aa8541a665b3f60979480b0431d626fd195dcda2"}, ] [package.dependencies] @@ -859,14 +760,13 @@ langgraph-checkpoint = ">=2.1.0,<3.0.0" [[package]] name = "langgraph-sdk" -version = "0.2.3" +version = "0.2.9" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "langgraph_sdk-0.2.3-py3-none-any.whl", hash = "sha256:059edfe2f62708c2e54239e170f5a33f796d456dbdbde64276c16cac8b97ba99"}, - {file = "langgraph_sdk-0.2.3.tar.gz", hash = "sha256:17398aeae0f937cae1c8eb9027ada2969abdb50fe8ed3246c78f543b679cf959"}, + {file = "langgraph_sdk-0.2.9-py3-none-any.whl", hash = "sha256:fbf302edadbf0fb343596f91c597794e936ef68eebc0d3e1d358b6f9f72a1429"}, + {file = "langgraph_sdk-0.2.9.tar.gz", hash = "sha256:b3bd04c6be4fa382996cd2be8fbc1e7cc94857d2bc6b6f4599a7f2a245975303"}, ] [package.dependencies] @@ -875,16 +775,14 @@ orjson = ">=3.10.1" [[package]] name = "langsmith" -version = "0.4.19" +version = "0.4.37" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "langsmith-0.4.19-py3-none-any.whl", hash = "sha256:4c50ae47e9f8430a06adb54bceaf32808f5e54fcb8186731bf7b2dab3fc30621"}, - {file = "langsmith-0.4.19.tar.gz", hash = "sha256:71916bef574f72c40887ce371a4502d80c80efc2a053df123f1347e79ea83dca"}, + {file = "langsmith-0.4.37-py3-none-any.whl", hash = "sha256:e34a94ce7277646299e4703a0f6e2d2c43647a28e8b800bb7ef82fd87a0ec766"}, + {file = "langsmith-0.4.37.tar.gz", hash = "sha256:d9a0eb6dd93f89843ac982c9f92be93cf2bcabbe19957f362c547766c7366c71"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] httpx = ">=0.23.0,<1" @@ -896,6 +794,7 @@ requests-toolbelt = ">=1.0.0" zstandard = ">=0.23.0" [package.extras] +claude-agent-sdk = ["claude-agent-sdk (>=0.1.0)"] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] openai-agents = ["openai-agents (>=0.0.3)"] otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] @@ -904,121 +803,147 @@ vcr = ["vcrpy (>=7.0.0)"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, - {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, - {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, - {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, - {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, - {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, - {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, - {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, - {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, - {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, - {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, - {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, - {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, - {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, - {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, - {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, +files = [ + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] @@ -1040,7 +965,6 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1052,7 +976,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1060,43 +983,40 @@ files = [ [[package]] name = "openai" -version = "1.102.0" +version = "2.5.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, - {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, + {file = "openai-2.5.0-py3-none-any.whl", hash = "sha256:21380e5f52a71666dbadbf322dd518bdf2b9d11ed0bb3f96bea17310302d6280"}, + {file = "openai-2.5.0.tar.gz", hash = "sha256:f8fa7611f96886a0f31ac6b97e58bc0ada494b255ee2cfd51c8eb502cfcb4814"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] anyio = ">=3.5.0,<5" distro = ">=1.7.0,<2" httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" +jiter = ">=0.10.0,<1" pydantic = ">=1.9.0,<3" sniffio = "*" tqdm = ">4" typing-extensions = ">=4.11,<5" [package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.8)"] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] [[package]] name = "opentelemetry-api" -version = "1.36.0" +version = "1.38.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, - {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, + {file = "opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582"}, + {file = "opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12"}, ] [package.dependencies] @@ -1105,50 +1025,47 @@ typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.36.0" +version = "1.38.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c"}, ] [package.dependencies] -opentelemetry-proto = "1.36.0" +opentelemetry-proto = "1.38.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.36.0" +version = "1.38.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b"}, ] [package.dependencies] googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.36.0" -opentelemetry-proto = "1.36.0" -opentelemetry-sdk = ">=1.36.0,<1.37.0" +opentelemetry-exporter-otlp-proto-common = "1.38.0" +opentelemetry-proto = "1.38.0" +opentelemetry-sdk = ">=1.38.0,<1.39.0" requests = ">=2.7,<3.0" typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-proto" -version = "1.36.0" +version = "1.38.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, - {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, + {file = "opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18"}, + {file = "opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468"}, ] [package.dependencies] @@ -1156,35 +1073,33 @@ protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.36.0" +version = "1.38.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, - {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, + {file = "opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b"}, + {file = "opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe"}, ] [package.dependencies] -opentelemetry-api = "1.36.0" -opentelemetry-semantic-conventions = "0.57b0" +opentelemetry-api = "1.38.0" +opentelemetry-semantic-conventions = "0.59b0" typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.57b0" +version = "0.59b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, - {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, + {file = "opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed"}, + {file = "opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0"}, ] [package.dependencies] -opentelemetry-api = "1.36.0" +opentelemetry-api = "1.38.0" typing-extensions = ">=4.5.0" [[package]] @@ -1193,7 +1108,6 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1279,57 +1193,70 @@ files = [ {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, ] -markers = {main = "extra == \"langchain\" and platform_python_implementation != \"PyPy\""} [[package]] name = "ormsgpack" -version = "1.10.0" -description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" +version = "1.11.0" +description = "" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "ormsgpack-1.10.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216"}, - {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373"}, - {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7058ef6092f995561bf9f71d6c9a4da867b6cc69d2e94cb80184f579a3ceed5"}, - {file = "ormsgpack-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6f3509c1b0e51b15552d314b1d409321718122e90653122ce4b997f01453a"}, - {file = "ormsgpack-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c1edafd5c72b863b1f875ec31c529f09c872a5ff6fe473b9dfaf188ccc3227"}, - {file = "ormsgpack-1.10.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c780b44107a547a9e9327270f802fa4d6b0f6667c9c03c3338c0ce812259a0f7"}, - {file = "ormsgpack-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:137aab0d5cdb6df702da950a80405eb2b7038509585e32b4e16289604ac7cb84"}, - {file = "ormsgpack-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:3e666cb63030538fa5cd74b1e40cb55b6fdb6e2981f024997a288bf138ebad07"}, - {file = "ormsgpack-1.10.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4bb7df307e17b36cbf7959cd642c47a7f2046ae19408c564e437f0ec323a7775"}, - {file = "ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8817ae439c671779e1127ee62f0ac67afdeaeeacb5f0db45703168aa74a2e4af"}, - {file = "ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f345f81e852035d80232e64374d3a104139d60f8f43c6c5eade35c4bac5590e"}, - {file = "ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21de648a1c7ef692bdd287fb08f047bd5371d7462504c0a7ae1553c39fee35e3"}, - {file = "ormsgpack-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3a7d844ae9cbf2112c16086dd931b2acefce14cefd163c57db161170c2bfa22b"}, - {file = "ormsgpack-1.10.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4d80585403d86d7f800cf3d0aafac1189b403941e84e90dd5102bb2b92bf9d5"}, - {file = "ormsgpack-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:da1de515a87e339e78a3ccf60e39f5fb740edac3e9e82d3c3d209e217a13ac08"}, - {file = "ormsgpack-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:57c4601812684024132cbb32c17a7d4bb46ffc7daf2fddf5b697391c2c4f142a"}, - {file = "ormsgpack-1.10.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4e159d50cd4064d7540e2bc6a0ab66eab70b0cc40c618b485324ee17037527c0"}, - {file = "ormsgpack-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb47c85f3a866e29279d801115b554af0fefc409e2ed8aa90aabfa77efe5cc6"}, - {file = "ormsgpack-1.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c28249574934534c9bd5dce5485c52f21bcea0ee44d13ece3def6e3d2c3798b5"}, - {file = "ormsgpack-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1957dcadbb16e6a981cd3f9caef9faf4c2df1125e2a1b702ee8236a55837ce07"}, - {file = "ormsgpack-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b29412558c740bf6bac156727aa85ac67f9952cd6f071318f29ee72e1a76044"}, - {file = "ormsgpack-1.10.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6933f350c2041ec189fe739f0ba7d6117c8772f5bc81f45b97697a84d03020dd"}, - {file = "ormsgpack-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a86de06d368fcc2e58b79dece527dc8ca831e0e8b9cec5d6e633d2777ec93d0"}, - {file = "ormsgpack-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:35fa9f81e5b9a0dab42e09a73f7339ecffdb978d6dbf9deb2ecf1e9fc7808722"}, - {file = "ormsgpack-1.10.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d816d45175a878993b7372bd5408e0f3ec5a40f48e2d5b9d8f1cc5d31b61f1f"}, - {file = "ormsgpack-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90345ccb058de0f35262893751c603b6376b05f02be2b6f6b7e05d9dd6d5643"}, - {file = "ormsgpack-1.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144b5e88f1999433e54db9d637bae6fe21e935888be4e3ac3daecd8260bd454e"}, - {file = "ormsgpack-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2190b352509d012915921cca76267db136cd026ddee42f1b0d9624613cc7058c"}, - {file = "ormsgpack-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:86fd9c1737eaba43d3bb2730add9c9e8b5fbed85282433705dd1b1e88ea7e6fb"}, - {file = "ormsgpack-1.10.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:33afe143a7b61ad21bb60109a86bb4e87fec70ef35db76b89c65b17e32da7935"}, - {file = "ormsgpack-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f23d45080846a7b90feabec0d330a9cc1863dc956728412e4f7986c80ab3a668"}, - {file = "ormsgpack-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:534d18acb805c75e5fba09598bf40abe1851c853247e61dda0c01f772234da69"}, - {file = "ormsgpack-1.10.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:efdb25cf6d54085f7ae557268d59fd2d956f1a09a340856e282d2960fe929f32"}, - {file = "ormsgpack-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddfcb30d4b1be2439836249d675f297947f4fb8efcd3eeb6fd83021d773cadc4"}, - {file = "ormsgpack-1.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee0944b6ccfd880beb1ca29f9442a774683c366f17f4207f8b81c5e24cadb453"}, - {file = "ormsgpack-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35cdff6a0d3ba04e40a751129763c3b9b57a602c02944138e4b760ec99ae80a1"}, - {file = "ormsgpack-1.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:599ccdabc19c618ef5de6e6f2e7f5d48c1f531a625fa6772313b8515bc710681"}, - {file = "ormsgpack-1.10.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:bf46f57da9364bd5eefd92365c1b78797f56c6f780581eecd60cd7b367f9b4d3"}, - {file = "ormsgpack-1.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b796f64fdf823dedb1e35436a4a6f889cf78b1aa42d3097c66e5adfd8c3bd72d"}, - {file = "ormsgpack-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:106253ac9dc08520951e556b3c270220fcb8b4fef0d30b71eedac4befa4de749"}, - {file = "ormsgpack-1.10.0.tar.gz", hash = "sha256:7f7a27efd67ef22d7182ec3b7fa7e9d147c3ad9be2a24656b23c989077e08b16"}, +files = [ + {file = "ormsgpack-1.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:03d4e658dd6e1882a552ce1d13cc7b49157414e7d56a4091fbe7823225b08cba"}, + {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb67eb913c2b703f0ed39607fc56e50724dd41f92ce080a586b4d6149eb3fe4"}, + {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e54175b92411f73a238e5653a998627f6660de3def37d9dd7213e0fd264ca56"}, + {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca2b197f4556e1823d1319869d4c5dc278be335286d2308b0ed88b59a5afcc25"}, + {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc62388262f58c792fe1e450e1d9dbcc174ed2fb0b43db1675dd7c5ff2319d6a"}, + {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c48bc10af74adfbc9113f3fb160dc07c61ad9239ef264c17e449eba3de343dc2"}, + {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a608d3a1d4fa4acdc5082168a54513cff91f47764cef435e81a483452f5f7647"}, + {file = "ormsgpack-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:97217b4f7f599ba45916b9c4c4b1d5656e8e2a4d91e2e191d72a7569d3c30923"}, + {file = "ormsgpack-1.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c7be823f47d8e36648d4bc90634b93f02b7d7cc7480081195f34767e86f181fb"}, + {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68accf15d1b013812755c0eb7a30e1fc2f81eb603a1a143bf0cda1b301cfa797"}, + {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:805d06fb277d9a4e503c0c707545b49cde66cbb2f84e5cf7c58d81dfc20d8658"}, + {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1e57cdf003e77acc43643bda151dc01f97147a64b11cdee1380bb9698a7601c"}, + {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37fc05bdaabd994097c62e2f3e08f66b03f856a640ede6dc5ea340bd15b77f4d"}, + {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a6e9db6c73eb46b2e4d97bdffd1368a66f54e6806b563a997b19c004ef165e1d"}, + {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9c44eae5ac0196ffc8b5ed497c75511056508f2303fa4d36b208eb820cf209e"}, + {file = "ormsgpack-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:11d0dfaf40ae7c6de4f7dbd1e4892e2e6a55d911ab1774357c481158d17371e4"}, + {file = "ormsgpack-1.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:0c63a3f7199a3099c90398a1bdf0cb577b06651a442dc5efe67f2882665e5b02"}, + {file = "ormsgpack-1.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3434d0c8d67de27d9010222de07fb6810fb9af3bb7372354ffa19257ac0eb83b"}, + {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2da5bd097e8dbfa4eb0d4ccfe79acd6f538dee4493579e2debfe4fc8f4ca89b"}, + {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fdbaa0a5a8606a486960b60c24f2d5235d30ac7a8b98eeaea9854bffef14dc3d"}, + {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3682f24f800c1837017ee90ce321086b2cbaef88db7d4cdbbda1582aa6508159"}, + {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcca21202bb05ccbf3e0e92f560ee59b9331182e4c09c965a28155efbb134993"}, + {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c30e5c4655ba46152d722ec7468e8302195e6db362ec1ae2c206bc64f6030e43"}, + {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7138a341f9e2c08c59368f03d3be25e8b87b3baaf10d30fb1f6f6b52f3d47944"}, + {file = "ormsgpack-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4bd8589b78a11026d47f4edf13c1ceab9088bb12451f34396afe6497db28a27"}, + {file = "ormsgpack-1.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:e5e746a1223e70f111d4001dab9585ac8639eee8979ca0c8db37f646bf2961da"}, + {file = "ormsgpack-1.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e7b36ab7b45cb95217ae1f05f1318b14a3e5ef73cb00804c0f06233f81a14e8"}, + {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43402d67e03a9a35cc147c8c03f0c377cad016624479e1ee5b879b8425551484"}, + {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64fd992f932764d6306b70ddc755c1bc3405c4c6a69f77a36acf7af1c8f5ada4"}, + {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0362fb7fe4a29c046c8ea799303079a09372653a1ce5a5a588f3bbb8088368d0"}, + {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:de2f7a65a9d178ed57be49eba3d0fc9b833c32beaa19dbd4ba56014d3c20b152"}, + {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f38cfae95461466055af966fc922d06db4e1654966385cda2828653096db34da"}, + {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c88396189d238f183cea7831b07a305ab5c90d6d29b53288ae11200bd956357b"}, + {file = "ormsgpack-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5403d1a945dd7c81044cebeca3f00a28a0f4248b33242a5d2d82111628043725"}, + {file = "ormsgpack-1.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c57357b8d43b49722b876edf317bdad9e6d52071b523fdd7394c30cd1c67d5a0"}, + {file = "ormsgpack-1.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d390907d90fd0c908211592c485054d7a80990697ef4dff4e436ac18e1aab98a"}, + {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6153c2e92e789509098e04c9aa116b16673bd88ec78fbe0031deeb34ab642d10"}, + {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2b2c2a065a94d742212b2018e1fecd8f8d72f3c50b53a97d1f407418093446d"}, + {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110e65b5340f3d7ef8b0009deae3c6b169437e6b43ad5a57fd1748085d29d2ac"}, + {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c27e186fca96ab34662723e65b420919910acbbc50fc8e1a44e08f26268cb0e0"}, + {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d56b1f877c13d499052d37a3db2378a97d5e1588d264f5040b3412aee23d742c"}, + {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c88e28cd567c0a3269f624b4ade28142d5e502c8e826115093c572007af5be0a"}, + {file = "ormsgpack-1.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:8811160573dc0a65f62f7e0792c4ca6b7108dfa50771edb93f9b84e2d45a08ae"}, + {file = "ormsgpack-1.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:23e30a8d3c17484cf74e75e6134322255bd08bc2b5b295cc9c442f4bae5f3c2d"}, + {file = "ormsgpack-1.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2905816502adfaf8386a01dd85f936cd378d243f4f5ee2ff46f67f6298dc90d5"}, + {file = "ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04402fb9a0a9b9f18fbafd6d5f8398ee99b3ec619fb63952d3a954bc9d47daa"}, + {file = "ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a025ec07ac52056ecfd9e57b5cbc6fff163f62cb9805012b56cda599157f8ef2"}, + {file = "ormsgpack-1.11.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:354c6a5039faf63b63d8f42ec7915583a4a56e10b319284370a5a89c4382d985"}, + {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7058c85cc13dd329bc7b528e38626c6babcd0066d6e9163330a1509fe0aa4707"}, + {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e15b634be324fb18dab7aa82ab929a0d57d42c12650ae3dedd07d8d31b17733"}, + {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6329e6eae9dfe600962739a6e060ea82885ec58b8338875c5ac35080da970f94"}, + {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b27546c28f92b9eb757620f7f1ed89fb7b07be3b9f4ba1b7de75761ec1c4bcc8"}, + {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:26a17919d9144b4ac7112dbbadef07927abbe436be2cf99a703a19afe7dd5c8b"}, + {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5352868ee4cdc00656bf216b56bc654f72ac3008eb36e12561f6337bb7104b45"}, + {file = "ormsgpack-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:2ffe36f1f441a40949e8587f5aa3d3fc9f100576925aab667117403eab494338"}, + {file = "ormsgpack-1.11.0.tar.gz", hash = "sha256:7c9988e78fedba3292541eb3bb274fa63044ef4da2ddb47259ea70c05dee4206"}, ] [[package]] @@ -1338,7 +1265,6 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1350,7 +1276,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1362,7 +1287,6 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1375,20 +1299,19 @@ pygments = ">=2.12.0" [[package]] name = "platformdirs" -version = "4.4.0" +version = "4.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, + {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, + {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] [[package]] name = "pluggy" @@ -1396,7 +1319,6 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1412,7 +1334,6 @@ version = "0.9.0" description = "A fast C-implemented library for Levenshtein distance" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, @@ -1477,7 +1398,6 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1492,156 +1412,172 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "6.32.0" +version = "6.33.0" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, - {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, - {file = "protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c"}, - {file = "protobuf-6.32.0-cp39-cp39-win32.whl", hash = "sha256:7db8ed09024f115ac877a1427557b838705359f047b2ff2f2b2364892d19dacb"}, - {file = "protobuf-6.32.0-cp39-cp39-win_amd64.whl", hash = "sha256:15eba1b86f193a407607112ceb9ea0ba9569aed24f93333fe9a497cf2fda37d3"}, - {file = "protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783"}, - {file = "protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2"}, + {file = "protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035"}, + {file = "protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee"}, + {file = "protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455"}, + {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90"}, + {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298"}, + {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef"}, + {file = "protobuf-6.33.0-cp39-cp39-win32.whl", hash = "sha256:cd33a8e38ea3e39df66e1bbc462b076d6e5ba3a4ebbde58219d777223a7873d3"}, + {file = "protobuf-6.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:c963e86c3655af3a917962c9619e1a6b9670540351d7af9439d06064e3317cc9"}, + {file = "protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995"}, + {file = "protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954"}, ] [[package]] name = "pydantic" -version = "2.11.9" +version = "2.12.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, - {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, + {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, + {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.33.2" -typing-extensions = ">=4.12.2" -typing-inspection = ">=0.4.0" +pydantic-core = "2.41.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, - {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +files = [ + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, + {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pygments" @@ -1649,7 +1585,6 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1664,7 +1599,6 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1684,20 +1618,18 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-asyncio" -version = "1.1.0" +version = "1.1.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, - {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, + {file = "pytest_asyncio-1.1.1-py3-none-any.whl", hash = "sha256:726339d30fcfde24691f589445b9b67d058b311ac632b1d704e97f20f1d878da"}, + {file = "pytest_asyncio-1.1.1.tar.gz", hash = "sha256:b72d215c38e2c91dbb32f275e0b5be69602d7869910e109360e375129960a649"}, ] [package.dependencies] backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} pytest = ">=8.2,<9" -typing-extensions = {version = ">=4.12", markers = "python_version < \"3.10\""} [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] @@ -1709,7 +1641,6 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1724,7 +1655,6 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1739,7 +1669,6 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1756,78 +1685,95 @@ testing = ["filelock"] [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] -markers = {main = "extra == \"langchain\""} +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, +] [[package]] name = "referencing" -version = "0.36.2" +version = "0.37.0" description = "JSON Referencing + Python" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, - {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, + {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, + {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, ] [package.dependencies] @@ -1837,99 +1783,126 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2025.7.34" +version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, - {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, - {file = "regex-2025.7.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95b4639c77d414efa93c8de14ce3f7965a94d007e068a94f9d4997bb9bd9c81f"}, - {file = "regex-2025.7.34-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7de1ceed5a5f84f342ba4a9f4ae589524adf9744b2ee61b5da884b5b659834"}, - {file = "regex-2025.7.34-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02e5860a250cd350c4933cf376c3bc9cb28948e2c96a8bc042aee7b985cfa26f"}, - {file = "regex-2025.7.34-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a5966220b9a1a88691282b7e4350e9599cf65780ca60d914a798cb791aa1177"}, - {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48fb045bbd4aab2418dc1ba2088a5e32de4bfe64e1457b948bb328a8dc2f1c2e"}, - {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20ff8433fa45e131f7316594efe24d4679c5449c0ca69d91c2f9d21846fdf064"}, - {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c436fd1e95c04c19039668cfb548450a37c13f051e8659f40aed426e36b3765f"}, - {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b85241d3cfb9f8a13cefdfbd58a2843f208f2ed2c88181bf84e22e0c7fc066d"}, - {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:075641c94126b064c65ab86e7e71fc3d63e7ff1bea1fb794f0773c97cdad3a03"}, - {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70645cad3407d103d1dbcb4841839d2946f7d36cf38acbd40120fee1682151e5"}, - {file = "regex-2025.7.34-cp310-cp310-win32.whl", hash = "sha256:3b836eb4a95526b263c2a3359308600bd95ce7848ebd3c29af0c37c4f9627cd3"}, - {file = "regex-2025.7.34-cp310-cp310-win_amd64.whl", hash = "sha256:cbfaa401d77334613cf434f723c7e8ba585df162be76474bccc53ae4e5520b3a"}, - {file = "regex-2025.7.34-cp310-cp310-win_arm64.whl", hash = "sha256:bca11d3c38a47c621769433c47f364b44e8043e0de8e482c5968b20ab90a3986"}, - {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8"}, - {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a"}, - {file = "regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68"}, - {file = "regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78"}, - {file = "regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719"}, - {file = "regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33"}, - {file = "regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083"}, - {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3"}, - {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d"}, - {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd"}, - {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a"}, - {file = "regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1"}, - {file = "regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a"}, - {file = "regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0"}, - {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50"}, - {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f"}, - {file = "regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130"}, - {file = "regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46"}, - {file = "regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4"}, - {file = "regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0"}, - {file = "regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b"}, - {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01"}, - {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77"}, - {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da"}, - {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282"}, - {file = "regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588"}, - {file = "regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62"}, - {file = "regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176"}, - {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5"}, - {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd"}, - {file = "regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b"}, - {file = "regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad"}, - {file = "regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59"}, - {file = "regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415"}, - {file = "regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f"}, - {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1"}, - {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c"}, - {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a"}, - {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0"}, - {file = "regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1"}, - {file = "regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997"}, - {file = "regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f"}, - {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a"}, - {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435"}, - {file = "regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac"}, - {file = "regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72"}, - {file = "regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e"}, - {file = "regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751"}, - {file = "regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4"}, - {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98"}, - {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7"}, - {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47"}, - {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e"}, - {file = "regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb"}, - {file = "regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae"}, - {file = "regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64"}, - {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd5edc3f453de727af267c7909d083e19f6426fc9dd149e332b6034f2a5611e6"}, - {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa1cdfb8db96ef20137de5587954c812821966c3e8b48ffc871e22d7ec0a4938"}, - {file = "regex-2025.7.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89c9504fc96268e8e74b0283e548f53a80c421182a2007e3365805b74ceef936"}, - {file = "regex-2025.7.34-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33be70d75fa05a904ee0dc43b650844e067d14c849df7e82ad673541cd465b5f"}, - {file = "regex-2025.7.34-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57d25b6732ea93eeb1d090e8399b6235ca84a651b52d52d272ed37d3d2efa0f1"}, - {file = "regex-2025.7.34-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:baf2fe122a3db1c0b9f161aa44463d8f7e33eeeda47bb0309923deb743a18276"}, - {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a764a83128af9c1a54be81485b34dca488cbcacefe1e1d543ef11fbace191e1"}, - {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7f663ccc4093877f55b51477522abd7299a14c5bb7626c5238599db6a0cb95d"}, - {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4913f52fbc7a744aaebf53acd8d3dc1b519e46ba481d4d7596de3c862e011ada"}, - {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:efac4db9e044d47fd3b6b0d40b6708f4dfa2d8131a5ac1d604064147c0f552fd"}, - {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7373afae7cfb716e3b8e15d0184510d518f9d21471f2d62918dbece85f2c588f"}, - {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9960d162f3fecf6af252534a1ae337e9c2e20d74469fed782903b24e2cc9d3d7"}, - {file = "regex-2025.7.34-cp39-cp39-win32.whl", hash = "sha256:95d538b10eb4621350a54bf14600cc80b514211d91a019dc74b8e23d2159ace5"}, - {file = "regex-2025.7.34-cp39-cp39-win_amd64.whl", hash = "sha256:f7f3071b5faa605b0ea51ec4bb3ea7257277446b053f4fd3ad02b1dcb4e64353"}, - {file = "regex-2025.7.34-cp39-cp39-win_arm64.whl", hash = "sha256:716a47515ba1d03f8e8a61c5013041c8c90f2e21f055203498105d7571b44531"}, - {file = "regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a"}, +files = [ + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"}, + {file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"}, + {file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"}, + {file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"}, + {file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"}, + {file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"}, + {file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"}, + {file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"}, + {file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"}, + {file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"}, + {file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"}, + {file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"}, + {file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"}, + {file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"}, + {file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"}, + {file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"}, + {file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"}, + {file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"}, + {file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"}, + {file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"}, + {file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"}, + {file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"}, + {file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"}, + {file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"}, + {file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"}, + {file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"}, ] [[package]] @@ -1938,7 +1911,6 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1960,12 +1932,10 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] -markers = {main = "extra == \"langchain\""} [package.dependencies] requests = ">=2.0.1,<3.0.0" @@ -1976,7 +1946,6 @@ version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, @@ -2137,31 +2106,30 @@ files = [ [[package]] name = "ruff" -version = "0.12.11" +version = "0.12.12" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065"}, - {file = "ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93"}, - {file = "ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8"}, - {file = "ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f"}, - {file = "ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000"}, - {file = "ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2"}, - {file = "ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39"}, - {file = "ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9"}, - {file = "ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3"}, - {file = "ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd"}, - {file = "ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea"}, - {file = "ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d"}, +files = [ + {file = "ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc"}, + {file = "ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727"}, + {file = "ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45"}, + {file = "ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5"}, + {file = "ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4"}, + {file = "ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23"}, + {file = "ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489"}, + {file = "ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee"}, + {file = "ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1"}, + {file = "ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d"}, + {file = "ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093"}, + {file = "ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6"}, ] [[package]] @@ -2170,121 +2138,21 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] -[[package]] -name = "sqlalchemy" -version = "2.0.43" -description = "Database Abstraction Library" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"langchain\"" -files = [ - {file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07097c0a1886c150ef2adba2ff7437e84d40c0f7dcb44a2c2b9c905ccfc6361c"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cdeff998cb294896a34e5b2f00e383e7c5c4ef3b4bfa375d9104723f15186443"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bcf0724a62a5670e5718957e05c56ec2d6850267ea859f8ad2481838f889b42c"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-win32.whl", hash = "sha256:c697575d0e2b0a5f0433f679bda22f63873821d991e95a90e9e52aae517b2e32"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-win_amd64.whl", hash = "sha256:d34c0f6dbefd2e816e8f341d0df7d4763d382e3f452423e752ffd1e213da2512"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70322986c0c699dca241418fcf18e637a4369e0ec50540a2b907b184c8bca069"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87accdbba88f33efa7b592dc2e8b2a9c2cdbca73db2f9d5c510790428c09c154"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c00e7845d2f692ebfc7d5e4ec1a3fd87698e4337d09e58d6749a16aedfdf8612"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:022e436a1cb39b13756cf93b48ecce7aa95382b9cfacceb80a7d263129dfd019"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5e73ba0d76eefc82ec0219d2301cb33bfe5205ed7a2602523111e2e56ccbd20"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c2e02f06c68092b875d5cbe4824238ab93a7fa35d9c38052c033f7ca45daa18"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-win32.whl", hash = "sha256:e7a903b5b45b0d9fa03ac6a331e1c1d6b7e0ab41c63b6217b3d10357b83c8b00"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-win_amd64.whl", hash = "sha256:4bf0edb24c128b7be0c61cd17eef432e4bef507013292415f3fb7023f02b7d4b"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e6aeb2e0932f32950cf56a8b4813cb15ff792fc0c9b3752eaf067cfe298496a"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f964a05356f4bca4112e6334ed7c208174511bd56e6b8fc86dad4d024d4185"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46293c39252f93ea0910aababa8752ad628bcce3a10d3f260648dd472256983f"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136063a68644eca9339d02e6693932116f6a8591ac013b0014479a1de664e40a"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6e2bf13d9256398d037fef09fd8bf9b0bf77876e22647d10761d35593b9ac547"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:44337823462291f17f994d64282a71c51d738fc9ef561bf265f1d0fd9116a782"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-win32.whl", hash = "sha256:13194276e69bb2af56198fef7909d48fd34820de01d9c92711a5fa45497cc7ed"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-win_amd64.whl", hash = "sha256:334f41fa28de9f9be4b78445e68530da3c5fa054c907176460c81494f4ae1f5e"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb5c832cc30663aeaf5e39657712f4c4241ad1f638d487ef7216258f6d41fe7"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11f43c39b4b2ec755573952bbcc58d976779d482f6f832d7f33a8d869ae891bf"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413391b2239db55be14fa4223034d7e13325a1812c8396ecd4f2c08696d5ccad"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c379e37b08c6c527181a397212346be39319fb64323741d23e46abd97a400d34"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03d73ab2a37d9e40dec4984d1813d7878e01dbdc742448d44a7341b7a9f408c7"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cee08f15d9e238ede42e9bbc1d6e7158d0ca4f176e4eab21f88ac819ae3bd7b"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-win32.whl", hash = "sha256:b3edaec7e8b6dc5cd94523c6df4f294014df67097c8217a89929c99975811414"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-win_amd64.whl", hash = "sha256:227119ce0a89e762ecd882dc661e0aa677a690c914e358f0dd8932a2e8b2765b"}, - {file = "sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc"}, - {file = "sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417"}, -] - -[package.dependencies] -greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] -aioodbc = ["aioodbc", "greenlet (>=1)"] -aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (>=1)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - [[package]] name = "tenacity" version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, ] -markers = {main = "extra == \"langchain\""} [package.extras] doc = ["reno", "sphinx"] @@ -2292,43 +2160,68 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tiktoken" -version = "0.11.0" +version = "0.12.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "tiktoken-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917"}, - {file = "tiktoken-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0"}, - {file = "tiktoken-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10331d08b5ecf7a780b4fe4d0281328b23ab22cdb4ff65e68d56caeda9940ecc"}, - {file = "tiktoken-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b062c82300341dc87e0258c69f79bed725f87e753c21887aea90d272816be882"}, - {file = "tiktoken-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:195d84bec46169af3b1349a1495c151d37a0ff4cba73fd08282736be7f92cc6c"}, - {file = "tiktoken-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe91581b0ecdd8783ce8cb6e3178f2260a3912e8724d2f2d49552b98714641a1"}, - {file = "tiktoken-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ae374c46afadad0f501046db3da1b36cd4dfbfa52af23c998773682446097cf"}, - {file = "tiktoken-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25a512ff25dc6c85b58f5dd4f3d8c674dc05f96b02d66cdacf628d26a4e4866b"}, - {file = "tiktoken-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2130127471e293d385179c1f3f9cd445070c0772be73cdafb7cec9a3684c0458"}, - {file = "tiktoken-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e43022bf2c33f733ea9b54f6a3f6b4354b909f5a73388fb1b9347ca54a069c"}, - {file = "tiktoken-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:adb4e308eb64380dc70fa30493e21c93475eaa11669dea313b6bbf8210bfd013"}, - {file = "tiktoken-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ece6b76bfeeb61a125c44bbefdfccc279b5288e6007fbedc0d32bfec602df2f2"}, - {file = "tiktoken-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fd9e6b23e860973cf9526544e220b223c60badf5b62e80a33509d6d40e6c8f5d"}, - {file = "tiktoken-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76d53cee2da71ee2731c9caa747398762bda19d7f92665e882fef229cb0b5b"}, - {file = "tiktoken-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef72aab3ea240646e642413cb363b73869fed4e604dcfd69eec63dc54d603e8"}, - {file = "tiktoken-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f929255c705efec7a28bf515e29dc74220b2f07544a8c81b8d69e8efc4578bd"}, - {file = "tiktoken-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61f1d15822e4404953d499fd1dcc62817a12ae9fb1e4898033ec8fe3915fdf8e"}, - {file = "tiktoken-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:45927a71ab6643dfd3ef57d515a5db3d199137adf551f66453be098502838b0f"}, - {file = "tiktoken-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a5f3f25ffb152ee7fec78e90a5e5ea5b03b4ea240beed03305615847f7a6ace2"}, - {file = "tiktoken-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dc6e9ad16a2a75b4c4be7208055a1f707c9510541d94d9cc31f7fbdc8db41d8"}, - {file = "tiktoken-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a0517634d67a8a48fd4a4ad73930c3022629a85a217d256a6e9b8b47439d1e4"}, - {file = "tiktoken-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fb4effe60574675118b73c6fbfd3b5868e5d7a1f570d6cc0d18724b09ecf318"}, - {file = "tiktoken-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94f984c9831fd32688aef4348803b0905d4ae9c432303087bae370dc1381a2b8"}, - {file = "tiktoken-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2177ffda31dec4023356a441793fed82f7af5291120751dee4d696414f54db0c"}, - {file = "tiktoken-0.11.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:13220f12c9e82e399377e768640ddfe28bea962739cc3a869cad98f42c419a89"}, - {file = "tiktoken-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f2db627f5c74477c0404b4089fd8a28ae22fa982a6f7d9c7d4c305c375218f3"}, - {file = "tiktoken-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2302772f035dceb2bcf8e55a735e4604a0b51a6dd50f38218ff664d46ec43807"}, - {file = "tiktoken-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b977989afe44c94bcc50db1f76971bb26dca44218bd203ba95925ef56f8e7a"}, - {file = "tiktoken-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:669a1aa1ad6ebf1b3c26b45deb346f345da7680f845b5ea700bba45c20dea24c"}, - {file = "tiktoken-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:e363f33c720a055586f730c00e330df4c7ea0024bf1c83a8a9a9dbc054c4f304"}, - {file = "tiktoken-0.11.0.tar.gz", hash = "sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a"}, +files = [ + {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, + {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, + {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}, + {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}, + {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}, + {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}, + {file = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}, + {file = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}, + {file = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}, + {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}, + {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}, + {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}, + {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}, + {file = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}, + {file = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}, + {file = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}, + {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}, + {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}, + {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}, + {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}, + {file = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}, + {file = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}, + {file = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}, + {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}, + {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}, + {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}, + {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}, + {file = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}, + {file = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}, + {file = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}, + {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}, + {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}, + {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}, + {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}, + {file = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}, + {file = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}, + {file = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}, + {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}, + {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}, + {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}, + {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}, + {file = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}, + {file = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}, + {file = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}, + {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}, + {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}, + {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}, + {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}, + {file = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}, + {file = "tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e"}, + {file = "tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179"}, + {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c"}, + {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7"}, + {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946"}, + {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec"}, + {file = "tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3"}, + {file = "tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}, ] [package.dependencies] @@ -2340,45 +2233,53 @@ blobfile = ["blobfile (>=2)"] [[package]] name = "tomli" -version = "2.2.1" +version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +files = [ + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] [[package]] @@ -2387,12 +2288,10 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] -markers = {main = "extra == \"openai\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -2410,7 +2309,6 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2418,14 +2316,13 @@ files = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] [package.dependencies] @@ -2437,28 +2334,26 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.34.0" +version = "20.35.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, - {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, + {file = "virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a"}, + {file = "virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44"}, ] [package.dependencies] @@ -2469,7 +2364,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "werkzeug" @@ -2477,7 +2372,6 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2495,7 +2389,6 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2582,135 +2475,151 @@ files = [ [[package]] name = "xxhash" -version = "3.5.0" +version = "3.6.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, - {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, - {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680"}, - {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da"}, - {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23"}, - {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196"}, - {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c"}, - {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482"}, - {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296"}, - {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415"}, - {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198"}, - {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442"}, - {file = "xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da"}, - {file = "xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9"}, - {file = "xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6"}, - {file = "xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1"}, - {file = "xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8"}, - {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166"}, - {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7"}, - {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623"}, - {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a"}, - {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88"}, - {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c"}, - {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2"}, - {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084"}, - {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d"}, - {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839"}, - {file = "xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da"}, - {file = "xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58"}, - {file = "xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3"}, - {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"}, - {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"}, - {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"}, - {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"}, - {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"}, - {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"}, - {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"}, - {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"}, - {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"}, - {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"}, - {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"}, - {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"}, - {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"}, - {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"}, - {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"}, - {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"}, - {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"}, - {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"}, - {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"}, - {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"}, - {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"}, - {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"}, - {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"}, - {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"}, - {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"}, - {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"}, - {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"}, - {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"}, - {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"}, - {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"}, - {file = "xxhash-3.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e5f70f6dca1d3b09bccb7daf4e087075ff776e3da9ac870f86ca316736bb4aa"}, - {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e76e83efc7b443052dd1e585a76201e40b3411fe3da7af4fe434ec51b2f163b"}, - {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33eac61d0796ca0591f94548dcfe37bb193671e0c9bcf065789b5792f2eda644"}, - {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ec70a89be933ea49222fafc3999987d7899fc676f688dd12252509434636622"}, - {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86b8e7f703ec6ff4f351cfdb9f428955859537125904aa8c963604f2e9d3e7"}, - {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0adfbd36003d9f86c8c97110039f7539b379f28656a04097e7434d3eaf9aa131"}, - {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:63107013578c8a730419adc05608756c3fa640bdc6abe806c3123a49fb829f43"}, - {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:683b94dbd1ca67557850b86423318a2e323511648f9f3f7b1840408a02b9a48c"}, - {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5d2a01dcce81789cf4b12d478b5464632204f4c834dc2d064902ee27d2d1f0ee"}, - {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a9d360a792cbcce2fe7b66b8d51274ec297c53cbc423401480e53b26161a290d"}, - {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f0b48edbebea1b7421a9c687c304f7b44d0677c46498a046079d445454504737"}, - {file = "xxhash-3.5.0-cp37-cp37m-win32.whl", hash = "sha256:7ccb800c9418e438b44b060a32adeb8393764da7441eb52aa2aa195448935306"}, - {file = "xxhash-3.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c3bc7bf8cb8806f8d1c9bf149c18708cb1c406520097d6b0a73977460ea03602"}, - {file = "xxhash-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74752ecaa544657d88b1d1c94ae68031e364a4d47005a90288f3bab3da3c970f"}, - {file = "xxhash-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dee1316133c9b463aa81aca676bc506d3f80d8f65aeb0bba2b78d0b30c51d7bd"}, - {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:602d339548d35a8579c6b013339fb34aee2df9b4e105f985443d2860e4d7ffaa"}, - {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:695735deeddfb35da1677dbc16a083445360e37ff46d8ac5c6fcd64917ff9ade"}, - {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1030a39ba01b0c519b1a82f80e8802630d16ab95dc3f2b2386a0b5c8ed5cbb10"}, - {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bc08f33c4966f4eb6590d6ff3ceae76151ad744576b5fc6c4ba8edd459fdec"}, - {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e0c19ee500482ddfb5d5570a0415f565d8ae2b3fd69c5dcfce8a58107b1c3"}, - {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f1abffa122452481a61c3551ab3c89d72238e279e517705b8b03847b1d93d738"}, - {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d5e9db7ef3ecbfc0b4733579cea45713a76852b002cf605420b12ef3ef1ec148"}, - {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:23241ff6423378a731d84864bf923a41649dc67b144debd1077f02e6249a0d54"}, - {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:82b833d5563fefd6fceafb1aed2f3f3ebe19f84760fdd289f8b926731c2e6e91"}, - {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a80ad0ffd78bef9509eee27b4a29e56f5414b87fb01a888353e3d5bda7038bd"}, - {file = "xxhash-3.5.0-cp38-cp38-win32.whl", hash = "sha256:50ac2184ffb1b999e11e27c7e3e70cc1139047e7ebc1aa95ed12f4269abe98d4"}, - {file = "xxhash-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:392f52ebbb932db566973693de48f15ce787cabd15cf6334e855ed22ea0be5b3"}, - {file = "xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301"}, - {file = "xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab"}, - {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f"}, - {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd"}, - {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc"}, - {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754"}, - {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6"}, - {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898"}, - {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833"}, - {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6"}, - {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af"}, - {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606"}, - {file = "xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4"}, - {file = "xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558"}, - {file = "xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e"}, - {file = "xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c"}, - {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986"}, - {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6"}, - {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b"}, - {file = "xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da"}, - {file = "xxhash-3.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b4154c00eb22e4d543f472cfca430e7962a0f1d0f3778334f2e08a7ba59363c"}, - {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d30bbc1644f726b825b3278764240f449d75f1a8bdda892e641d4a688b1494ae"}, - {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa0b72f2423e2aa53077e54a61c28e181d23effeaafd73fcb9c494e60930c8e"}, - {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13de2b76c1835399b2e419a296d5b38dc4855385d9e96916299170085ef72f57"}, - {file = "xxhash-3.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0691bfcc4f9c656bcb96cc5db94b4d75980b9d5589f2e59de790091028580837"}, - {file = "xxhash-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:297595fe6138d4da2c8ce9e72a04d73e58725bb60f3a19048bc96ab2ff31c692"}, - {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1276d369452040cbb943300dc8abeedab14245ea44056a2943183822513a18"}, - {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2061188a1ba352fc699c82bff722f4baacb4b4b8b2f0c745d2001e56d0dfb514"}, - {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c384c434021e4f62b8d9ba0bc9467e14d394893077e2c66d826243025e1f81"}, - {file = "xxhash-3.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e6a4dd644d72ab316b580a1c120b375890e4c52ec392d4aef3c63361ec4d77d1"}, - {file = "xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9"}, - {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1"}, - {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f"}, - {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0"}, - {file = "xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240"}, - {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, +files = [ + {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, + {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, + {file = "xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8"}, + {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058"}, + {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2"}, + {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc"}, + {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc"}, + {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07"}, + {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4"}, + {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06"}, + {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4"}, + {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b"}, + {file = "xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b"}, + {file = "xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb"}, + {file = "xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d"}, + {file = "xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a"}, + {file = "xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa"}, + {file = "xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248"}, + {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62"}, + {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f"}, + {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e"}, + {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8"}, + {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0"}, + {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77"}, + {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c"}, + {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b"}, + {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3"}, + {file = "xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd"}, + {file = "xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef"}, + {file = "xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7"}, + {file = "xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c"}, + {file = "xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204"}, + {file = "xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490"}, + {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2"}, + {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa"}, + {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0"}, + {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2"}, + {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9"}, + {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e"}, + {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374"}, + {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d"}, + {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae"}, + {file = "xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb"}, + {file = "xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c"}, + {file = "xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829"}, + {file = "xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec"}, + {file = "xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1"}, + {file = "xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6"}, + {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263"}, + {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546"}, + {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89"}, + {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d"}, + {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7"}, + {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db"}, + {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42"}, + {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11"}, + {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd"}, + {file = "xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799"}, + {file = "xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392"}, + {file = "xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6"}, + {file = "xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702"}, + {file = "xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db"}, + {file = "xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54"}, + {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f"}, + {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5"}, + {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1"}, + {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee"}, + {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd"}, + {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729"}, + {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292"}, + {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf"}, + {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033"}, + {file = "xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec"}, + {file = "xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8"}, + {file = "xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746"}, + {file = "xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e"}, + {file = "xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405"}, + {file = "xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3"}, + {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6"}, + {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063"}, + {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7"}, + {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b"}, + {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd"}, + {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0"}, + {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152"}, + {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11"}, + {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5"}, + {file = "xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f"}, + {file = "xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad"}, + {file = "xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679"}, + {file = "xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4"}, + {file = "xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67"}, + {file = "xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad"}, + {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b"}, + {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b"}, + {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca"}, + {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a"}, + {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99"}, + {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3"}, + {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6"}, + {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93"}, + {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518"}, + {file = "xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119"}, + {file = "xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f"}, + {file = "xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95"}, + {file = "xxhash-3.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dac94fad14a3d1c92affb661021e1d5cbcf3876be5f5b4d90730775ccb7ac41"}, + {file = "xxhash-3.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6965e0e90f1f0e6cb78da568c13d4a348eeb7f40acfd6d43690a666a459458b8"}, + {file = "xxhash-3.6.0-cp38-cp38-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab89a6b80f22214b43d98693c30da66af910c04f9858dd39c8e570749593d7e"}, + {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4903530e866b7a9c1eadfd3fa2fbe1b97d3aed4739a80abf506eb9318561c850"}, + {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4da8168ae52c01ac64c511d6f4a709479da8b7a4a1d7621ed51652f93747dffa"}, + {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97460eec202017f719e839a0d3551fbc0b2fcc9c6c6ffaa5af85bbd5de432788"}, + {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aae0c9df92e7fa46fbb738737324a563c727990755ec1965a6a339ea10a1df"}, + {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d50101e57aad86f4344ca9b32d091a2135a9d0a4396f19133426c88025b09f1"}, + {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9085e798c163ce310d91f8aa6b325dda3c2944c93c6ce1edb314030d4167cc65"}, + {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a87f271a33fad0e5bf3be282be55d78df3a45ae457950deb5241998790326f87"}, + {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:9e040d3e762f84500961791fa3709ffa4784d4dcd7690afc655c095e02fff05f"}, + {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b0359391c3dad6de872fefb0cf5b69d55b0655c55ee78b1bb7a568979b2ce96b"}, + {file = "xxhash-3.6.0-cp38-cp38-win32.whl", hash = "sha256:e4ff728a2894e7f436b9e94c667b0f426b9c74b71f900cf37d5468c6b5da0536"}, + {file = "xxhash-3.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:01be0c5b500c5362871fc9cfdf58c69b3e5c4f531a82229ddb9eb1eb14138004"}, + {file = "xxhash-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc604dc06027dbeb8281aeac5899c35fcfe7c77b25212833709f0bff4ce74d2a"}, + {file = "xxhash-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:277175a73900ad43a8caeb8b99b9604f21fe8d7c842f2f9061a364a7e220ddb7"}, + {file = "xxhash-3.6.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfbc5b91397c8c2972fdac13fb3e4ed2f7f8ccac85cd2c644887557780a9b6e2"}, + {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2762bfff264c4e73c0e507274b40634ff465e025f0eaf050897e88ec8367575d"}, + {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f171a900d59d51511209f7476933c34a0c2c711078d3c80e74e0fe4f38680ec"}, + {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:780b90c313348f030b811efc37b0fa1431163cb8db8064cf88a7936b6ce5f222"}, + {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b242455eccdfcd1fa4134c431a30737d2b4f045770f8fe84356b3469d4b919"}, + {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a75ffc1bd5def584129774c158e108e5d768e10b75813f2b32650bb041066ed6"}, + {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1fc1ed882d1e8df932a66e2999429ba6cc4d5172914c904ab193381fba825360"}, + {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:44e342e8cc11b4e79dae5c57f2fb6360c3c20cc57d32049af8f567f5b4bcb5f4"}, + {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c2f9ccd5c4be370939a2e17602fbc49995299203da72a3429db013d44d590e86"}, + {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02ea4cb627c76f48cd9fb37cf7ab22bd51e57e1b519807234b473faebe526796"}, + {file = "xxhash-3.6.0-cp39-cp39-win32.whl", hash = "sha256:6551880383f0e6971dc23e512c9ccc986147ce7bfa1cd2e4b520b876c53e9f3d"}, + {file = "xxhash-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:7c35c4cdc65f2a29f34425c446f2f5cdcd0e3c34158931e1cc927ece925ab802"}, + {file = "xxhash-3.6.0-cp39-cp39-win_arm64.whl", hash = "sha256:ffc578717a347baf25be8397cb10d2528802d24f94cfc005c0e44fef44b5cdd6"}, + {file = "xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0"}, + {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296"}, + {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13"}, + {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd"}, + {file = "xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d"}, + {file = "xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6"}, ] [[package]] @@ -2719,14 +2628,13 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2735,122 +2643,116 @@ type = ["pytest-mypy"] [[package]] name = "zstandard" -version = "0.24.0" +version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "zstandard-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af1394c2c5febc44e0bbf0fc6428263fa928b50d1b1982ce1d870dc793a8e5f4"}, - {file = "zstandard-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e941654cef13a1d53634ec30933722eda11f44f99e1d0bc62bbce3387580d50"}, - {file = "zstandard-0.24.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:561123d05681197c0e24eb8ab3cfdaf299e2b59c293d19dad96e1610ccd8fbc6"}, - {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0f6d9a146e07458cb41423ca2d783aefe3a3a97fe72838973c13b8f1ecc7343a"}, - {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf02f915fa7934ea5dfc8d96757729c99a8868b7c340b97704795d6413cf5fe6"}, - {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:35f13501a8accf834457d8e40e744568287a215818778bc4d79337af2f3f0d97"}, - {file = "zstandard-0.24.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92be52ca4e6e604f03d5daa079caec9e04ab4cbf6972b995aaebb877d3d24e13"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c9c3cba57f5792532a3df3f895980d47d78eda94b0e5b800651b53e96e0b604"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dd91b0134a32dfcd8be504e8e46de44ad0045a569efc25101f2a12ccd41b5759"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d6975f2d903bc354916a17b91a7aaac7299603f9ecdb788145060dde6e573a16"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7ac6e4d727521d86d20ec291a3f4e64a478e8a73eaee80af8f38ec403e77a409"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:87ae1684bc3c02d5c35884b3726525eda85307073dbefe68c3c779e104a59036"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:7de5869e616d426b56809be7dc6dba4d37b95b90411ccd3de47f421a42d4d42c"}, - {file = "zstandard-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:388aad2d693707f4a0f6cc687eb457b33303d6b57ecf212c8ff4468c34426892"}, - {file = "zstandard-0.24.0-cp310-cp310-win32.whl", hash = "sha256:962ea3aecedcc944f8034812e23d7200d52c6e32765b8da396eeb8b8ffca71ce"}, - {file = "zstandard-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:869bf13f66b124b13be37dd6e08e4b728948ff9735308694e0b0479119e08ea7"}, - {file = "zstandard-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:addfc23e3bd5f4b6787b9ca95b2d09a1a67ad5a3c318daaa783ff90b2d3a366e"}, - {file = "zstandard-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b005bcee4be9c3984b355336283afe77b2defa76ed6b89332eced7b6fa68b68"}, - {file = "zstandard-0.24.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:3f96a9130171e01dbb6c3d4d9925d604e2131a97f540e223b88ba45daf56d6fb"}, - {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd0d3d16e63873253bad22b413ec679cf6586e51b5772eb10733899832efec42"}, - {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b7a8c30d9bf4bd5e4dcfe26900bef0fcd9749acde45cdf0b3c89e2052fda9a13"}, - {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:52cd7d9fa0a115c9446abb79b06a47171b7d916c35c10e0c3aa6f01d57561382"}, - {file = "zstandard-0.24.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0f6fc2ea6e07e20df48752e7700e02e1892c61f9a6bfbacaf2c5b24d5ad504b"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e46eb6702691b24ddb3e31e88b4a499e31506991db3d3724a85bd1c5fc3cfe4e"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5e3b9310fd7f0d12edc75532cd9a56da6293840c84da90070d692e0bb15f186"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76cdfe7f920738ea871f035568f82bad3328cbc8d98f1f6988264096b5264efd"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f2fe35ec84908dddf0fbf66b35d7c2878dbe349552dd52e005c755d3493d61c"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:aa705beb74ab116563f4ce784fa94771f230c05d09ab5de9c397793e725bb1db"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:aadf32c389bb7f02b8ec5c243c38302b92c006da565e120dfcb7bf0378f4f848"}, - {file = "zstandard-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e40cd0fc734aa1d4bd0e7ad102fd2a1aefa50ce9ef570005ffc2273c5442ddc3"}, - {file = "zstandard-0.24.0-cp311-cp311-win32.whl", hash = "sha256:cda61c46343809ecda43dc620d1333dd7433a25d0a252f2dcc7667f6331c7b61"}, - {file = "zstandard-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b95fc06489aa9388400d1aab01a83652bc040c9c087bd732eb214909d7fb0dd"}, - {file = "zstandard-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad9fd176ff6800a0cf52bcf59c71e5de4fa25bf3ba62b58800e0f84885344d34"}, - {file = "zstandard-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2bda8f2790add22773ee7a4e43c90ea05598bffc94c21c40ae0a9000b0133c3"}, - {file = "zstandard-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc76de75300f65b8eb574d855c12518dc25a075dadb41dd18f6322bda3fe15d5"}, - {file = "zstandard-0.24.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d2b3b4bda1a025b10fe0269369475f420177f2cb06e0f9d32c95b4873c9f80b8"}, - {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b84c6c210684286e504022d11ec294d2b7922d66c823e87575d8b23eba7c81f"}, - {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c59740682a686bf835a1a4d8d0ed1eefe31ac07f1c5a7ed5f2e72cf577692b00"}, - {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6324fde5cf5120fbf6541d5ff3c86011ec056e8d0f915d8e7822926a5377193a"}, - {file = "zstandard-0.24.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51a86bd963de3f36688553926a84e550d45d7f9745bd1947d79472eca27fcc75"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d82ac87017b734f2fb70ff93818c66f0ad2c3810f61040f077ed38d924e19980"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92ea7855d5bcfb386c34557516c73753435fb2d4a014e2c9343b5f5ba148b5d8"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3adb4b5414febf074800d264ddf69ecade8c658837a83a19e8ab820e924c9933"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6374feaf347e6b83ec13cc5dcfa70076f06d8f7ecd46cc71d58fac798ff08b76"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:13fc548e214df08d896ee5f29e1f91ee35db14f733fef8eabea8dca6e451d1e2"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0a416814608610abf5488889c74e43ffa0343ca6cf43957c6b6ec526212422da"}, - {file = "zstandard-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0d66da2649bb0af4471699aeb7a83d6f59ae30236fb9f6b5d20fb618ef6c6777"}, - {file = "zstandard-0.24.0-cp312-cp312-win32.whl", hash = "sha256:ff19efaa33e7f136fe95f9bbcc90ab7fb60648453b03f95d1de3ab6997de0f32"}, - {file = "zstandard-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc05f8a875eb651d1cc62e12a4a0e6afa5cd0cc231381adb830d2e9c196ea895"}, - {file = "zstandard-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:b04c94718f7a8ed7cdd01b162b6caa1954b3c9d486f00ecbbd300f149d2b2606"}, - {file = "zstandard-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e4ebb000c0fe24a6d0f3534b6256844d9dbf042fdf003efe5cf40690cf4e0f3e"}, - {file = "zstandard-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:498f88f5109666c19531f0243a90d2fdd2252839cd6c8cc6e9213a3446670fa8"}, - {file = "zstandard-0.24.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0a9e95ceb180ccd12a8b3437bac7e8a8a089c9094e39522900a8917745542184"}, - {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bcf69e0bcddbf2adcfafc1a7e864edcc204dd8171756d3a8f3340f6f6cc87b7b"}, - {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:10e284748a7e7fbe2815ca62a9d6e84497d34cfdd0143fa9e8e208efa808d7c4"}, - {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1bda8a85e5b9d5e73af2e61b23609a8cc1598c1b3b2473969912979205a1ff25"}, - {file = "zstandard-0.24.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b14bc92af065d0534856bf1b30fc48753163ea673da98857ea4932be62079b1"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:b4f20417a4f511c656762b001ec827500cbee54d1810253c6ca2df2c0a307a5f"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:337572a7340e1d92fd7fb5248c8300d0e91071002d92e0b8cabe8d9ae7b58159"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df4be1cf6e8f0f2bbe2a3eabfff163ef592c84a40e1a20a8d7db7f27cfe08fc2"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6885ae4b33aee8835dbdb4249d3dfec09af55e705d74d9b660bfb9da51baaa8b"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:663848a8bac4fdbba27feea2926049fdf7b55ec545d5b9aea096ef21e7f0b079"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:05d27c953f2e0a3ecc8edbe91d6827736acc4c04d0479672e0400ccdb23d818c"}, - {file = "zstandard-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77b8b7b98893eaf47da03d262816f01f251c2aa059c063ed8a45c50eada123a5"}, - {file = "zstandard-0.24.0-cp313-cp313-win32.whl", hash = "sha256:cf7fbb4e54136e9a03c7ed7691843c4df6d2ecc854a2541f840665f4f2bb2edd"}, - {file = "zstandard-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:d64899cc0f33a8f446f1e60bffc21fa88b99f0e8208750d9144ea717610a80ce"}, - {file = "zstandard-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:57be3abb4313e0dd625596376bbb607f40059d801d51c1a1da94d7477e63b255"}, - {file = "zstandard-0.24.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b7fa260dd2731afd0dfa47881c30239f422d00faee4b8b341d3e597cface1483"}, - {file = "zstandard-0.24.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e05d66239d14a04b4717998b736a25494372b1b2409339b04bf42aa4663bf251"}, - {file = "zstandard-0.24.0-cp314-cp314-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:622e1e04bd8a085994e02313ba06fbcf4f9ed9a488c6a77a8dbc0692abab6a38"}, - {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:55872e818598319f065e8192ebefecd6ac05f62a43f055ed71884b0a26218f41"}, - {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bb2446a55b3a0fd8aa02aa7194bd64740015464a2daaf160d2025204e1d7c282"}, - {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2825a3951f945fb2613ded0f517d402b1e5a68e87e0ee65f5bd224a8333a9a46"}, - {file = "zstandard-0.24.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09887301001e7a81a3618156bc1759e48588de24bddfdd5b7a4364da9a8fbc20"}, - {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:98ca91dc9602cf351497d5600aa66e6d011a38c085a8237b370433fcb53e3409"}, - {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e69f8e534b4e254f523e2f9d4732cf9c169c327ca1ce0922682aac9a5ee01155"}, - {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:444633b487a711e34f4bccc46a0c5dfbe1aee82c1a511e58cdc16f6bd66f187c"}, - {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f7d3fe9e1483171e9183ffdb1fab07c5fef80a9c3840374a38ec2ab869ebae20"}, - {file = "zstandard-0.24.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:27b6fa72b57824a3f7901fc9cc4ce1c1c834b28f3a43d1d4254c64c8f11149d4"}, - {file = "zstandard-0.24.0-cp314-cp314-win32.whl", hash = "sha256:fdc7a52a4cdaf7293e10813fd6a3abc0c7753660db12a3b864ab1fb5a0c60c16"}, - {file = "zstandard-0.24.0-cp314-cp314-win_amd64.whl", hash = "sha256:656ed895b28c7e42dd5b40dfcea3217cfc166b6b7eef88c3da2f5fc62484035b"}, - {file = "zstandard-0.24.0-cp314-cp314-win_arm64.whl", hash = "sha256:0101f835da7de08375f380192ff75135527e46e3f79bef224e3c49cb640fef6a"}, - {file = "zstandard-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52788e7c489069e317fde641de41b757fa0ddc150e06488f153dd5daebac7192"}, - {file = "zstandard-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec194197e90ca063f5ecb935d6c10063d84208cac5423c07d0f1a09d1c2ea42b"}, - {file = "zstandard-0.24.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e91a4e5d62da7cb3f53e04fe254f1aa41009af578801ee6477fe56e7bef74ee2"}, - {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fc67eb15ed573950bc6436a04b3faea6c36c7db98d2db030d48391c6736a0dc"}, - {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f6ae9fc67e636fc0fa9adee39db87dfbdeabfa8420bc0e678a1ac8441e01b22b"}, - {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ab2357353894a5ec084bb8508ff892aa43fb7fe8a69ad310eac58221ee7f72aa"}, - {file = "zstandard-0.24.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f578fab202f4df67a955145c3e3ca60ccaaaf66c97808545b2625efeecdef10"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c39d2b6161f3c5c5d12e9207ecf1006bb661a647a97a6573656b09aaea3f00ef"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dc5654586613aebe5405c1ba180e67b3f29e7d98cf3187c79efdcc172f39457"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b91380aefa9c7ac831b011368daf378d3277e0bdeb6bad9535e21251e26dd55a"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:010302face38c9a909b8934e3bf6038266d6afc69523f3efa023c5cb5d38271b"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:3aa3b4344b206941385a425ea25e6dd63e5cb0f535a4b88d56e3f8902086be9e"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:63d39b161000aeeaa06a1cb77c9806e939bfe460dfd593e4cbf24e6bc717ae94"}, - {file = "zstandard-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed8345b504df1cab280af923ef69ec0d7d52f7b22f78ec7982fde7c33a43c4f"}, - {file = "zstandard-0.24.0-cp39-cp39-win32.whl", hash = "sha256:1e133a9dd51ac0bcd5fd547ba7da45a58346dbc63def883f999857b0d0c003c4"}, - {file = "zstandard-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:8ecd3b1f7a601f79e0cd20c26057d770219c0dc2f572ea07390248da2def79a4"}, - {file = "zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f"}, -] -markers = {main = "extra == \"langchain\""} +files = [ + {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, + {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74"}, + {file = "zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa"}, + {file = "zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e"}, + {file = "zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c"}, + {file = "zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7"}, + {file = "zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4"}, + {file = "zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2"}, + {file = "zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137"}, + {file = "zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b"}, + {file = "zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa"}, + {file = "zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd"}, + {file = "zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01"}, + {file = "zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9"}, + {file = "zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94"}, + {file = "zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf"}, + {file = "zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09"}, + {file = "zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5"}, + {file = "zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049"}, + {file = "zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3"}, + {file = "zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088"}, + {file = "zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12"}, + {file = "zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2"}, + {file = "zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d"}, + {file = "zstandard-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0"}, + {file = "zstandard-0.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27"}, + {file = "zstandard-0.25.0-cp39-cp39-win32.whl", hash = "sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649"}, + {file = "zstandard-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860"}, + {file = "zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b"}, +] [package.extras] -cffi = ["cffi (>=1.17) ; python_version >= \"3.13\" and platform_python_implementation != \"PyPy\""] - -[extras] -langchain = ["langchain"] -openai = ["openai"] +cffi = ["cffi (>=1.17,<2.0)", "cffi (>=2.0.0b)"] [metadata] -lock-version = "2.1" -python-versions = ">=3.9,<4.0" -content-hash = "83ae81e7b9fd90ae8000dc0ac491ff766b899b166a5fc895043d0555267e288c" +lock-version = "2.0" +python-versions = ">=3.10,<4.0" +content-hash = "cfda3b10ea654d3aa01a0d4292631380b85dcaa982e726b6e6f575f15d9a22da" diff --git a/pyproject.toml b/pyproject.toml index dad96000e..2f622a5d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,13 +8,12 @@ license = "MIT" readme = "README.md" [tool.poetry.dependencies] -python = ">=3.9,<4.0" +python = ">=3.10,<4.0" httpx = ">=0.15.4,<1.0" pydantic = ">=1.10.7, <3.0" backoff = ">=1.10.0" openai = { version = ">=0.27.8", optional = true } wrapt = "^1.14" -langchain = { version = ">=0.0.309", optional = true } packaging = ">=23.2,<26.0" requests = "^2" opentelemetry-api = "^1.33.1" @@ -31,16 +30,13 @@ pytest-httpserver = "^1.0.8" ruff = ">=0.1.8,<0.13.0" mypy = "^1.0.0" langchain-openai = ">=0.0.5,<0.4" -langgraph = ">=0.2.62,<0.7.0" +langchain = ">=1" +langgraph = ">=1" autoevals = "^0.0.130" [tool.poetry.group.docs.dependencies] pdoc = "^15.0.4" -[tool.poetry.extras] -openai = ["openai"] -langchain = ["langchain"] - [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 7217c0a8d..c64a4adc1 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -3,7 +3,7 @@ from concurrent.futures import ThreadPoolExecutor from typing import Sequence -from langchain import PromptTemplate +from langchain_core.prompts import PromptTemplate from langchain_openai import OpenAI from langfuse import Langfuse, observe diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 5803d531b..0eac5c617 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -7,7 +7,7 @@ from typing import Optional import pytest -from langchain.prompts import ChatPromptTemplate +from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from opentelemetry import trace diff --git a/tests/test_json.py b/tests/test_json.py index afcacfc76..26b6919fa 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -7,7 +7,7 @@ from unittest.mock import patch import pytest -from langchain.schema.messages import HumanMessage +from langchain.messages import HumanMessage from pydantic import BaseModel import langfuse @@ -31,8 +31,9 @@ def test_json_encoder(): } result = json.dumps(obj, cls=EventSerializer) + print(result) assert ( - '{"foo": "bar", "bar": "2021-01-01T00:00:00Z", "date": "2024-01-01", "messages": [{"content": "I love programming!", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}]}' + '{"foo": "bar", "bar": "2021-01-01T00:00:00Z", "date": "2024-01-01", "messages": [{"content": "I love programming!", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null}]}' in result ) diff --git a/tests/test_langchain.py b/tests/test_langchain.py index deac5de7d..b4cf828b2 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -1,23 +1,13 @@ -import os import random import string import time from time import sleep -from typing import Any, Dict, List, Literal, Mapping, Optional +from typing import Any, Dict, Literal import pytest -from langchain.chains import ( - ConversationChain, - LLMChain, - SimpleSequentialChain, -) -from langchain.chains.openai_functions import create_openai_fn_chain -from langchain.memory import ConversationBufferMemory -from langchain.prompts import ChatPromptTemplate, PromptTemplate -from langchain.schema import HumanMessage, SystemMessage -from langchain_core.callbacks.manager import CallbackManagerForLLMRun -from langchain_core.language_models.llms import LLM +from langchain.messages import HumanMessage, SystemMessage from langchain_core.output_parsers import StrOutputParser +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate from langchain_core.runnables.base import RunnableLambda from langchain_core.tools import StructuredTool, tool from langchain_openai import ChatOpenAI, OpenAI @@ -31,59 +21,6 @@ from tests.utils import create_uuid, encode_file_to_base64, get_api -def test_callback_generated_from_trace_chain(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="parent") as span: - trace_id = span.trace_id - handler = CallbackHandler() - - llm = OpenAI(openai_api_key=os.environ.get("OPENAI_API_KEY")) - template = """You are a playwright. Given the title of play, it is your job to write a synopsis for that title. - Title: {title} - Playwright: This is a synopsis for the above play:""" - - prompt_template = PromptTemplate(input_variables=["title"], template=template) - synopsis_chain = LLMChain(llm=llm, prompt=prompt_template) - - synopsis_chain.run("Tragedy at sunset on the beach", callbacks=[handler]) - - langfuse.flush() - - trace = get_api().trace.get(trace_id) - - assert trace.input is None - assert trace.output is None - - assert len(trace.observations) == 3 - - langchain_span = list( - filter( - lambda o: o.type == "CHAIN" and o.name == "LLMChain", - trace.observations, - ) - )[0] - - assert langchain_span.input is not None - assert langchain_span.output is not None - - langchain_generation_span = list( - filter( - lambda o: o.type == "GENERATION" and o.name == "OpenAI", - trace.observations, - ) - )[0] - - assert langchain_generation_span.parent_observation_id == langchain_span.id - assert langchain_generation_span.usage_details["input"] > 0 - assert langchain_generation_span.usage_details["output"] > 0 - assert langchain_generation_span.usage_details["total"] > 0 - assert langchain_generation_span.input is not None - assert langchain_generation_span.input != "" - assert langchain_generation_span.output is not None - assert langchain_generation_span.output != "" - - def test_callback_generated_from_trace_chat(): langfuse = Langfuse() @@ -103,7 +40,7 @@ def test_callback_generated_from_trace_chat(): ), ] - chat(messages, callbacks=[handler]) + chat.invoke(messages, config={"callbacks": [handler]}) langfuse.flush() @@ -219,44 +156,6 @@ def test_basic_chat_openai(): assert generation.output is not None -def test_callback_retriever_conversational_with_memory(): - langfuse = Langfuse() - - with langfuse.start_as_current_span( - name="retriever_conversational_with_memory_test" - ) as span: - trace_id = span.trace_id - handler = CallbackHandler() - - llm = OpenAI(openai_api_key=os.environ.get("OPENAI_API_KEY")) - conversation = ConversationChain( - llm=llm, - verbose=True, - memory=ConversationBufferMemory(), - callbacks=[handler], - ) - conversation.predict(input="Hi there!", callbacks=[handler]) - - handler.client.flush() - - trace = get_api().trace.get(trace_id) - - # Add 1 to account for the wrapping span - assert len(trace.observations) == 3 - - generations = list(filter(lambda x: x.type == "GENERATION", trace.observations)) - assert len(generations) == 1 - - for generation in generations: - assert generation.input is not None - assert generation.output is not None - assert generation.input != "" - assert generation.output != "" - assert generation.usage_details["total"] is not None - assert generation.usage_details["input"] is not None - assert generation.usage_details["output"] is not None - - def test_callback_simple_openai(): langfuse = Langfuse() @@ -343,253 +242,6 @@ def test_callback_multiple_invocations_on_different_traces(): assert generation.output != "" -def test_callback_openai_functions_python(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="openai_functions_python_test") as span: - trace_id = span.trace_id - handler = CallbackHandler() - - llm = ChatOpenAI(model="gpt-4", temperature=0) - prompt = ChatPromptTemplate.from_messages( - [ - ( - "system", - "You are a world class algorithm for extracting information in structured formats.", - ), - ( - "human", - "Use the given format to extract information from the following input: {input}", - ), - ("human", "Tip: Make sure to answer in the correct format"), - ] - ) - - class OptionalFavFood(BaseModel): - """Either a food or null.""" - - food: Optional[str] = Field( - None, - description="Either the name of a food or null. Should be null if the food isn't known.", - ) - - def record_person(name: str, age: int, fav_food: OptionalFavFood) -> str: - """Record some basic identifying information about a person. - - Args: - name: The person's name. - age: The person's age in years. - fav_food: An OptionalFavFood object that either contains the person's favorite food or a null value. - Food should be null if it's not known. - """ - return f"Recording person {name} of age {age} with favorite food {fav_food.food}!" - - def record_dog(name: str, color: str, fav_food: OptionalFavFood) -> str: - """Record some basic identifying information about a dog. - - Args: - name: The dog's name. - color: The dog's color. - fav_food: An OptionalFavFood object that either contains the dog's favorite food or a null value. - Food should be null if it's not known. - """ - return ( - f"Recording dog {name} of color {color} with favorite food {fav_food}!" - ) - - chain = create_openai_fn_chain( - [record_person, record_dog], llm, prompt, callbacks=[handler] - ) - chain.run( - "I can't find my dog Henry anywhere, he's a small brown beagle. Could you send a message about him?", - callbacks=[handler], - ) - - handler.client.flush() - - trace = get_api().trace.get(trace_id) - - # Add 1 to account for the wrapping span - assert len(trace.observations) == 3 - - generations = list(filter(lambda x: x.type == "GENERATION", trace.observations)) - assert len(generations) > 0 - - for generation in generations: - assert generation.input is not None - assert generation.output is not None - assert generation.input == [ - { - "role": "system", - "content": "You are a world class algorithm for extracting information in structured formats.", - }, - { - "role": "user", - "content": "Use the given format to extract information from the following input: I can't find my dog Henry anywhere, he's a small brown beagle. Could you send a message about him?", - }, - { - "role": "user", - "content": "Tip: Make sure to answer in the correct format", - }, - ] - assert generation.output == { - "role": "assistant", - "content": "", - "additional_kwargs": { - "function_call": { - "arguments": '{\n "name": "Henry",\n "color": "brown",\n "fav_food": {\n "food": null\n }\n}', - "name": "record_dog", - }, - "refusal": None, - }, - } - assert generation.usage_details["total"] is not None - assert generation.usage_details["input"] is not None - assert generation.usage_details["output"] is not None - - -def test_agent_executor_chain(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="agent_executor_chain_test") as span: - trace_id = span.trace_id - from langchain.agents import AgentExecutor, create_react_agent - from langchain.tools import tool - - prompt = PromptTemplate.from_template(""" - Answer the following questions as best you can. You have access to the following tools: - - {tools} - - Use the following format: - - Question: the input question you must answer - Thought: you should always think about what to do - Action: the action to take, should be one of [{tool_names}] - Action Input: the input to the action - Observation: the result of the action - ... (this Thought/Action/Action Input/Observation can repeat N times) - Thought: I now know the final answer - Final Answer: the final answer to the original input question - - Begin! - - Question: {input} - Thought:{agent_scratchpad} - """) - - callback = CallbackHandler() - llm = OpenAI(temperature=0) - - @tool - def get_word_length(word: str) -> int: - """Returns the length of a word.""" - return len(word) - - tools = [get_word_length] - agent = create_react_agent(llm, tools, prompt) - agent_executor = AgentExecutor( - agent=agent, tools=tools, handle_parsing_errors=True - ) - - agent_executor.invoke( - {"input": "what is the length of the word LangFuse?"}, - config={"callbacks": [callback]}, - ) - - callback.client.flush() - - trace = get_api().trace.get(trace_id) - - generations = list(filter(lambda x: x.type == "GENERATION", trace.observations)) - assert len(generations) > 0 - - for generation in generations: - assert generation.input is not None - assert generation.output is not None - assert generation.input != "" - assert generation.output != "" - assert generation.usage_details["total"] is not None - assert generation.usage_details["input"] is not None - assert generation.usage_details["output"] is not None - - -def test_unimplemented_model(): - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="unimplemented_model_test") as span: - trace_id = span.trace_id - callback = CallbackHandler() - - class CustomLLM(LLM): - n: int - - @property - def _llm_type(self) -> str: - return "custom" - - def _call( - self, - prompt: str, - stop: Optional[List[str]] = None, - run_manager: Optional[CallbackManagerForLLMRun] = None, - **kwargs: Any, - ) -> str: - if stop is not None: - raise ValueError("stop kwargs are not permitted.") - return "This is a great text, which i can take characters from "[ - : self.n - ] - - @property - def _identifying_params(self) -> Mapping[str, Any]: - """Get the identifying parameters.""" - return {"n": self.n} - - custom_llm = CustomLLM(n=10) - - llm = OpenAI(openai_api_key=os.environ.get("OPENAI_API_KEY")) - template = """You are a playwright. Given the title of play, it is your job to write a synopsis for that title. - Title: {title} - Playwright: This is a synopsis for the above play:""" - - prompt_template = PromptTemplate(input_variables=["title"], template=template) - synopsis_chain = LLMChain(llm=llm, prompt=prompt_template) - - template = """You are a play critic from the New York Times. - Given the synopsis of play, it is your job to write a review for that play. - - Play Synopsis: - {synopsis} - Review from a New York Times play critic of the above play:""" - prompt_template = PromptTemplate( - input_variables=["synopsis"], template=template - ) - custom_llm_chain = LLMChain(llm=custom_llm, prompt=prompt_template) - - sequential_chain = SimpleSequentialChain( - chains=[custom_llm_chain, synopsis_chain] - ) - sequential_chain.run("This is a foobar thing", callbacks=[callback]) - - callback.client.flush() - - trace = get_api().trace.get(trace_id) - - # Add 1 to account for the wrapping span - assert len(trace.observations) == 6 - - custom_generation = list( - filter( - lambda x: x.type == "GENERATION" and x.name == "CustomLLM", - trace.observations, - ) - )[0] - - assert custom_generation.output == "This is a" - assert custom_generation.model is None - - def test_openai_instruct_usage(): langfuse = Langfuse() @@ -1343,92 +995,3 @@ def test_cached_token_usage(): ) < 0.0001 ) - - -def test_langchain_automatic_observation_types(): - """Test that LangChain components automatically get correct observation types: - AGENT, TOOL, GENERATION, RETRIEVER, CHAIN - """ - langfuse = Langfuse() - - with langfuse.start_as_current_span(name="observation_types_test_agent") as span: - trace_id = span.trace_id - handler = CallbackHandler() - - from langchain.agents import AgentExecutor, create_react_agent - from langchain.tools import tool - - # for type TOOL - @tool - def test_tool(x: str) -> str: - """Process input string.""" - return f"processed {x}" - - # for type GENERATION - llm = ChatOpenAI(temperature=0) - tools = [test_tool] - - prompt = PromptTemplate.from_template(""" - Answer: {input} - - Tools: {tools} - Tool names: {tool_names} - - Question: {input} - {agent_scratchpad} - """) - - # for type AGENT - agent = create_react_agent(llm, tools, prompt) - agent_executor = AgentExecutor( - agent=agent, tools=tools, handle_parsing_errors=True, max_iterations=1 - ) - - try: - agent_executor.invoke({"input": "hello"}, {"callbacks": [handler]}) - except Exception: - pass - - try: - test_tool.invoke("simple input", {"callbacks": [handler]}) - except Exception: - pass - - from langchain_core.prompts import PromptTemplate as CorePromptTemplate - - # for type CHAIN - chain_prompt = CorePromptTemplate.from_template("Answer: {question}") - simple_chain = chain_prompt | llm - - try: - simple_chain.invoke({"question": "hi"}, {"callbacks": [handler]}) - except Exception: - pass - - # for type RETRIEVER - from langchain_core.documents import Document - from langchain_core.retrievers import BaseRetriever - - class SimpleRetriever(BaseRetriever): - def _get_relevant_documents(self, query: str, *, run_manager): - return [Document(page_content="test doc")] - - try: - SimpleRetriever().invoke("query", {"callbacks": [handler]}) - except Exception: - pass - - handler.client.flush() - trace = get_api().trace.get(trace_id) - - # Validate all expected observation types are created - types_found = {obs.type for obs in trace.observations} - expected_types = {"AGENT", "TOOL", "CHAIN", "RETRIEVER", "GENERATION"} - - for obs_type in expected_types: - obs_count = len([obs for obs in trace.observations if obs.type == obs_type]) - assert obs_count > 0, f"Expected {obs_type} observations, found {obs_count}" - - assert expected_types.issubset( - types_found - ), f"Missing types: {expected_types - types_found}" diff --git a/tests/test_langchain_integration.py b/tests/test_langchain_integration.py index c45ec98e0..599860b50 100644 --- a/tests/test_langchain_integration.py +++ b/tests/test_langchain_integration.py @@ -1,8 +1,8 @@ import types import pytest -from langchain.prompts import ChatPromptTemplate, PromptTemplate -from langchain.schema import StrOutputParser +from langchain_core.output_parsers import StrOutputParser +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate from langchain_openai import ChatOpenAI, OpenAI from langfuse import Langfuse diff --git a/tests/test_prompt_compilation.py b/tests/test_prompt_compilation.py index 10a4cd990..039556c5d 100644 --- a/tests/test_prompt_compilation.py +++ b/tests/test_prompt_compilation.py @@ -1,5 +1,5 @@ import pytest -from langchain.prompts import ChatPromptTemplate, PromptTemplate +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate from langfuse.api.resources.prompts import ChatMessage, Prompt_Chat from langfuse.model import ( From 6e52a652ed52d2e38d665664f63795d3786ee8a2 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:44:53 +0200 Subject: [PATCH 082/296] chore: release v3.8.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 801fe542c..e8019aa85 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.7.0" +__version__ = "3.8.0" diff --git a/pyproject.toml b/pyproject.toml index 2f622a5d1..c2aab6b1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.7.0" +version = "3.8.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 1319ddf85eaf71497a267f3ed33797c996da5edc Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 20 Oct 2025 15:49:50 +0200 Subject: [PATCH 083/296] feat(api): update API spec from langfuse/langfuse f5e96a9 (#1408) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 20 ++ langfuse/api/client.py | 8 + langfuse/api/reference.md | 180 +++++++++- langfuse/api/resources/__init__.py | 22 ++ langfuse/api/resources/ingestion/client.py | 4 +- langfuse/api/resources/observations/client.py | 54 +++ .../api/resources/opentelemetry/__init__.py | 23 ++ .../api/resources/opentelemetry/client.py | 317 ++++++++++++++++++ .../resources/opentelemetry/types/__init__.py | 21 ++ .../opentelemetry/types/otel_attribute.py | 55 +++ .../types/otel_attribute_value.py | 72 ++++ .../opentelemetry/types/otel_resource.py | 52 +++ .../opentelemetry/types/otel_resource_span.py | 60 ++++ .../opentelemetry/types/otel_scope.py | 62 ++++ .../opentelemetry/types/otel_scope_span.py | 56 ++++ .../opentelemetry/types/otel_span.py | 104 ++++++ .../types/otel_trace_response.py | 44 +++ langfuse/api/resources/prompts/__init__.py | 2 + .../api/resources/prompts/types/__init__.py | 2 + .../resources/prompts/types/prompt_meta.py | 6 + .../resources/prompts/types/prompt_type.py | 19 ++ 21 files changed, 1180 insertions(+), 3 deletions(-) create mode 100644 langfuse/api/resources/opentelemetry/__init__.py create mode 100644 langfuse/api/resources/opentelemetry/client.py create mode 100644 langfuse/api/resources/opentelemetry/types/__init__.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_attribute.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_attribute_value.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_resource.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_resource_span.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_scope.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_scope_span.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_span.py create mode 100644 langfuse/api/resources/opentelemetry/types/otel_trace_response.py create mode 100644 langfuse/api/resources/prompts/types/prompt_type.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 3dd6de4e4..ce613a95d 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -136,6 +136,14 @@ OptionalObservationBody, OrganizationProject, OrganizationProjectsResponse, + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + OtelTraceResponse, PaginatedAnnotationQueueItems, PaginatedAnnotationQueues, PaginatedDatasetItems, @@ -153,6 +161,7 @@ Prompt, PromptMeta, PromptMetaListResponse, + PromptType, Prompt_Chat, Prompt_Text, ResourceMeta, @@ -221,6 +230,7 @@ metrics, models, observations, + opentelemetry, organizations, projects, prompt_version, @@ -370,6 +380,14 @@ "OptionalObservationBody", "OrganizationProject", "OrganizationProjectsResponse", + "OtelAttribute", + "OtelAttributeValue", + "OtelResource", + "OtelResourceSpan", + "OtelScope", + "OtelScopeSpan", + "OtelSpan", + "OtelTraceResponse", "PaginatedAnnotationQueueItems", "PaginatedAnnotationQueues", "PaginatedDatasetItems", @@ -387,6 +405,7 @@ "Prompt", "PromptMeta", "PromptMetaListResponse", + "PromptType", "Prompt_Chat", "Prompt_Text", "ResourceMeta", @@ -455,6 +474,7 @@ "metrics", "models", "observations", + "opentelemetry", "organizations", "projects", "prompt_version", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 619e649fa..646279b5a 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -30,6 +30,10 @@ from .resources.metrics.client import AsyncMetricsClient, MetricsClient from .resources.models.client import AsyncModelsClient, ModelsClient from .resources.observations.client import AsyncObservationsClient, ObservationsClient +from .resources.opentelemetry.client import ( + AsyncOpentelemetryClient, + OpentelemetryClient, +) from .resources.organizations.client import ( AsyncOrganizationsClient, OrganizationsClient, @@ -136,6 +140,7 @@ def __init__( self.metrics = MetricsClient(client_wrapper=self._client_wrapper) self.models = ModelsClient(client_wrapper=self._client_wrapper) self.observations = ObservationsClient(client_wrapper=self._client_wrapper) + self.opentelemetry = OpentelemetryClient(client_wrapper=self._client_wrapper) self.organizations = OrganizationsClient(client_wrapper=self._client_wrapper) self.projects = ProjectsClient(client_wrapper=self._client_wrapper) self.prompt_version = PromptVersionClient(client_wrapper=self._client_wrapper) @@ -240,6 +245,9 @@ def __init__( self.metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) self.models = AsyncModelsClient(client_wrapper=self._client_wrapper) self.observations = AsyncObservationsClient(client_wrapper=self._client_wrapper) + self.opentelemetry = AsyncOpentelemetryClient( + client_wrapper=self._client_wrapper + ) self.organizations = AsyncOrganizationsClient( client_wrapper=self._client_wrapper ) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index f2e9baa07..199c9421b 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -2442,7 +2442,7 @@ client.health.health() **Legacy endpoint for batch ingestion for Langfuse Observability.** --> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry +-> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). Learn more: https://langfuse.com/integrations/native/opentelemetry Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. @@ -3604,6 +3604,184 @@ client.observations.get_many()
+**filter:** `typing.Optional[str]` + +JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). +Each filter condition has the following structure: +```json +[ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } +] +``` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+ +
+ + + + + + +## Opentelemetry +
client.opentelemetry.export_traces(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +**OpenTelemetry Traces Ingestion Endpoint** + +This endpoint implements the OTLP/HTTP specification for trace ingestion, providing native OpenTelemetry integration for Langfuse Observability. + +**Supported Formats:** +- Binary Protobuf: `Content-Type: application/x-protobuf` +- JSON Protobuf: `Content-Type: application/json` +- Supports gzip compression via `Content-Encoding: gzip` header + +**Specification Compliance:** +- Conforms to [OTLP/HTTP Trace Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) +- Implements `ExportTraceServiceRequest` message format + +**Documentation:** +- Integration guide: https://langfuse.com/integrations/native/opentelemetry +- Data model: https://langfuse.com/docs/observability/data-model +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, +) +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.opentelemetry.export_traces( + resource_spans=[ + OtelResourceSpan( + resource=OtelResource( + attributes=[ + OtelAttribute( + key="service.name", + value=OtelAttributeValue( + string_value="my-service", + ), + ), + OtelAttribute( + key="service.version", + value=OtelAttributeValue( + string_value="1.0.0", + ), + ), + ], + ), + scope_spans=[ + OtelScopeSpan( + scope=OtelScope( + name="langfuse-sdk", + version="2.60.3", + ), + spans=[ + OtelSpan( + trace_id="0123456789abcdef0123456789abcdef", + span_id="0123456789abcdef", + name="my-operation", + kind=1, + start_time_unix_nano="1747872000000000000", + end_time_unix_nano="1747872001000000000", + attributes=[ + OtelAttribute( + key="langfuse.observation.type", + value=OtelAttributeValue( + string_value="generation", + ), + ) + ], + status={}, + ) + ], + ) + ], + ) + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**resource_spans:** `typing.Sequence[OtelResourceSpan]` — Array of resource spans containing trace data as defined in the OTLP specification + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index ae77cad9d..49e7a40ee 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -15,6 +15,7 @@ metrics, models, observations, + opentelemetry, organizations, projects, prompt_version, @@ -175,6 +176,16 @@ from .metrics import MetricsResponse from .models import CreateModelRequest, PaginatedModels from .observations import Observations, ObservationsViews +from .opentelemetry import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + OtelTraceResponse, +) from .organizations import ( DeleteMembershipRequest, MembershipDeletionResponse, @@ -210,6 +221,7 @@ Prompt, PromptMeta, PromptMetaListResponse, + PromptType, Prompt_Chat, Prompt_Text, TextPrompt, @@ -389,6 +401,14 @@ "OptionalObservationBody", "OrganizationProject", "OrganizationProjectsResponse", + "OtelAttribute", + "OtelAttributeValue", + "OtelResource", + "OtelResourceSpan", + "OtelScope", + "OtelScopeSpan", + "OtelSpan", + "OtelTraceResponse", "PaginatedAnnotationQueueItems", "PaginatedAnnotationQueues", "PaginatedDatasetItems", @@ -406,6 +426,7 @@ "Prompt", "PromptMeta", "PromptMetaListResponse", + "PromptType", "Prompt_Chat", "Prompt_Text", "ResourceMeta", @@ -474,6 +495,7 @@ "metrics", "models", "observations", + "opentelemetry", "organizations", "projects", "prompt_version", diff --git a/langfuse/api/resources/ingestion/client.py b/langfuse/api/resources/ingestion/client.py index d5aa2f952..c009c507b 100644 --- a/langfuse/api/resources/ingestion/client.py +++ b/langfuse/api/resources/ingestion/client.py @@ -33,7 +33,7 @@ def batch( """ **Legacy endpoint for batch ingestion for Langfuse Observability.** - -> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry + -> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). Learn more: https://langfuse.com/integrations/native/opentelemetry Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. @@ -151,7 +151,7 @@ async def batch( """ **Legacy endpoint for batch ingestion for Langfuse Observability.** - -> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry + -> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). Learn more: https://langfuse.com/integrations/native/opentelemetry Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. diff --git a/langfuse/api/resources/observations/client.py b/langfuse/api/resources/observations/client.py index b21981bb4..22c923399 100644 --- a/langfuse/api/resources/observations/client.py +++ b/langfuse/api/resources/observations/client.py @@ -107,6 +107,7 @@ def get_many( from_start_time: typing.Optional[dt.datetime] = None, to_start_time: typing.Optional[dt.datetime] = None, version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> ObservationsViews: """ @@ -145,6 +146,31 @@ def get_many( version : typing.Optional[str] Optional filter to only include observations with a certain version. + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -186,6 +212,7 @@ def get_many( if to_start_time is not None else None, "version": version, + "filter": filter, }, request_options=request_options, ) @@ -311,6 +338,7 @@ async def get_many( from_start_time: typing.Optional[dt.datetime] = None, to_start_time: typing.Optional[dt.datetime] = None, version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> ObservationsViews: """ @@ -349,6 +377,31 @@ async def get_many( version : typing.Optional[str] Optional filter to only include observations with a certain version. + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -398,6 +451,7 @@ async def main() -> None: if to_start_time is not None else None, "version": version, + "filter": filter, }, request_options=request_options, ) diff --git a/langfuse/api/resources/opentelemetry/__init__.py b/langfuse/api/resources/opentelemetry/__init__.py new file mode 100644 index 000000000..bada2052f --- /dev/null +++ b/langfuse/api/resources/opentelemetry/__init__.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + OtelTraceResponse, +) + +__all__ = [ + "OtelAttribute", + "OtelAttributeValue", + "OtelResource", + "OtelResourceSpan", + "OtelScope", + "OtelScopeSpan", + "OtelSpan", + "OtelTraceResponse", +] diff --git a/langfuse/api/resources/opentelemetry/client.py b/langfuse/api/resources/opentelemetry/client.py new file mode 100644 index 000000000..de17949d4 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/client.py @@ -0,0 +1,317 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from .types.otel_resource_span import OtelResourceSpan +from .types.otel_trace_response import OtelTraceResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class OpentelemetryClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def export_traces( + self, + *, + resource_spans: typing.Sequence[OtelResourceSpan], + request_options: typing.Optional[RequestOptions] = None, + ) -> OtelTraceResponse: + """ + **OpenTelemetry Traces Ingestion Endpoint** + + This endpoint implements the OTLP/HTTP specification for trace ingestion, providing native OpenTelemetry integration for Langfuse Observability. + + **Supported Formats:** + - Binary Protobuf: `Content-Type: application/x-protobuf` + - JSON Protobuf: `Content-Type: application/json` + - Supports gzip compression via `Content-Encoding: gzip` header + + **Specification Compliance:** + - Conforms to [OTLP/HTTP Trace Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) + - Implements `ExportTraceServiceRequest` message format + + **Documentation:** + - Integration guide: https://langfuse.com/integrations/native/opentelemetry + - Data model: https://langfuse.com/docs/observability/data-model + + Parameters + ---------- + resource_spans : typing.Sequence[OtelResourceSpan] + Array of resource spans containing trace data as defined in the OTLP specification + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OtelTraceResponse + + Examples + -------- + from langfuse import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + ) + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.opentelemetry.export_traces( + resource_spans=[ + OtelResourceSpan( + resource=OtelResource( + attributes=[ + OtelAttribute( + key="service.name", + value=OtelAttributeValue( + string_value="my-service", + ), + ), + OtelAttribute( + key="service.version", + value=OtelAttributeValue( + string_value="1.0.0", + ), + ), + ], + ), + scope_spans=[ + OtelScopeSpan( + scope=OtelScope( + name="langfuse-sdk", + version="2.60.3", + ), + spans=[ + OtelSpan( + trace_id="0123456789abcdef0123456789abcdef", + span_id="0123456789abcdef", + name="my-operation", + kind=1, + start_time_unix_nano="1747872000000000000", + end_time_unix_nano="1747872001000000000", + attributes=[ + OtelAttribute( + key="langfuse.observation.type", + value=OtelAttributeValue( + string_value="generation", + ), + ) + ], + status={}, + ) + ], + ) + ], + ) + ], + ) + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/otel/v1/traces", + method="POST", + json={"resourceSpans": resource_spans}, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(OtelTraceResponse, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncOpentelemetryClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def export_traces( + self, + *, + resource_spans: typing.Sequence[OtelResourceSpan], + request_options: typing.Optional[RequestOptions] = None, + ) -> OtelTraceResponse: + """ + **OpenTelemetry Traces Ingestion Endpoint** + + This endpoint implements the OTLP/HTTP specification for trace ingestion, providing native OpenTelemetry integration for Langfuse Observability. + + **Supported Formats:** + - Binary Protobuf: `Content-Type: application/x-protobuf` + - JSON Protobuf: `Content-Type: application/json` + - Supports gzip compression via `Content-Encoding: gzip` header + + **Specification Compliance:** + - Conforms to [OTLP/HTTP Trace Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) + - Implements `ExportTraceServiceRequest` message format + + **Documentation:** + - Integration guide: https://langfuse.com/integrations/native/opentelemetry + - Data model: https://langfuse.com/docs/observability/data-model + + Parameters + ---------- + resource_spans : typing.Sequence[OtelResourceSpan] + Array of resource spans containing trace data as defined in the OTLP specification + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OtelTraceResponse + + Examples + -------- + import asyncio + + from langfuse import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + ) + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.opentelemetry.export_traces( + resource_spans=[ + OtelResourceSpan( + resource=OtelResource( + attributes=[ + OtelAttribute( + key="service.name", + value=OtelAttributeValue( + string_value="my-service", + ), + ), + OtelAttribute( + key="service.version", + value=OtelAttributeValue( + string_value="1.0.0", + ), + ), + ], + ), + scope_spans=[ + OtelScopeSpan( + scope=OtelScope( + name="langfuse-sdk", + version="2.60.3", + ), + spans=[ + OtelSpan( + trace_id="0123456789abcdef0123456789abcdef", + span_id="0123456789abcdef", + name="my-operation", + kind=1, + start_time_unix_nano="1747872000000000000", + end_time_unix_nano="1747872001000000000", + attributes=[ + OtelAttribute( + key="langfuse.observation.type", + value=OtelAttributeValue( + string_value="generation", + ), + ) + ], + status={}, + ) + ], + ) + ], + ) + ], + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/otel/v1/traces", + method="POST", + json={"resourceSpans": resource_spans}, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(OtelTraceResponse, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/opentelemetry/types/__init__.py b/langfuse/api/resources/opentelemetry/types/__init__.py new file mode 100644 index 000000000..4ca603db6 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +from .otel_attribute import OtelAttribute +from .otel_attribute_value import OtelAttributeValue +from .otel_resource import OtelResource +from .otel_resource_span import OtelResourceSpan +from .otel_scope import OtelScope +from .otel_scope_span import OtelScopeSpan +from .otel_span import OtelSpan +from .otel_trace_response import OtelTraceResponse + +__all__ = [ + "OtelAttribute", + "OtelAttributeValue", + "OtelResource", + "OtelResourceSpan", + "OtelScope", + "OtelScopeSpan", + "OtelSpan", + "OtelTraceResponse", +] diff --git a/langfuse/api/resources/opentelemetry/types/otel_attribute.py b/langfuse/api/resources/opentelemetry/types/otel_attribute.py new file mode 100644 index 000000000..91b9e2b70 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_attribute.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .otel_attribute_value import OtelAttributeValue + + +class OtelAttribute(pydantic_v1.BaseModel): + """ + Key-value attribute pair for resources, scopes, or spans + """ + + key: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Attribute key (e.g., "service.name", "langfuse.observation.type") + """ + + value: typing.Optional[OtelAttributeValue] = pydantic_v1.Field(default=None) + """ + Attribute value + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_attribute_value.py b/langfuse/api/resources/opentelemetry/types/otel_attribute_value.py new file mode 100644 index 000000000..51f026495 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_attribute_value.py @@ -0,0 +1,72 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class OtelAttributeValue(pydantic_v1.BaseModel): + """ + Attribute value wrapper supporting different value types + """ + + string_value: typing.Optional[str] = pydantic_v1.Field( + alias="stringValue", default=None + ) + """ + String value + """ + + int_value: typing.Optional[int] = pydantic_v1.Field(alias="intValue", default=None) + """ + Integer value + """ + + double_value: typing.Optional[float] = pydantic_v1.Field( + alias="doubleValue", default=None + ) + """ + Double value + """ + + bool_value: typing.Optional[bool] = pydantic_v1.Field( + alias="boolValue", default=None + ) + """ + Boolean value + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_resource.py b/langfuse/api/resources/opentelemetry/types/otel_resource.py new file mode 100644 index 000000000..0d76d5a15 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_resource.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .otel_attribute import OtelAttribute + + +class OtelResource(pydantic_v1.BaseModel): + """ + Resource attributes identifying the source of telemetry + """ + + attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic_v1.Field( + default=None + ) + """ + Resource attributes like service.name, service.version, etc. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_resource_span.py b/langfuse/api/resources/opentelemetry/types/otel_resource_span.py new file mode 100644 index 000000000..e270ba7d8 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_resource_span.py @@ -0,0 +1,60 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .otel_resource import OtelResource +from .otel_scope_span import OtelScopeSpan + + +class OtelResourceSpan(pydantic_v1.BaseModel): + """ + Represents a collection of spans from a single resource as per OTLP specification + """ + + resource: typing.Optional[OtelResource] = pydantic_v1.Field(default=None) + """ + Resource information + """ + + scope_spans: typing.Optional[typing.List[OtelScopeSpan]] = pydantic_v1.Field( + alias="scopeSpans", default=None + ) + """ + Array of scope spans + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_scope.py b/langfuse/api/resources/opentelemetry/types/otel_scope.py new file mode 100644 index 000000000..71e9b75b8 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_scope.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .otel_attribute import OtelAttribute + + +class OtelScope(pydantic_v1.BaseModel): + """ + Instrumentation scope information + """ + + name: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Instrumentation scope name + """ + + version: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Instrumentation scope version + """ + + attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic_v1.Field( + default=None + ) + """ + Additional scope attributes + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_scope_span.py b/langfuse/api/resources/opentelemetry/types/otel_scope_span.py new file mode 100644 index 000000000..854951a60 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_scope_span.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .otel_scope import OtelScope +from .otel_span import OtelSpan + + +class OtelScopeSpan(pydantic_v1.BaseModel): + """ + Collection of spans from a single instrumentation scope + """ + + scope: typing.Optional[OtelScope] = pydantic_v1.Field(default=None) + """ + Instrumentation scope information + """ + + spans: typing.Optional[typing.List[OtelSpan]] = pydantic_v1.Field(default=None) + """ + Array of spans + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_span.py b/langfuse/api/resources/opentelemetry/types/otel_span.py new file mode 100644 index 000000000..08b7be7fb --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_span.py @@ -0,0 +1,104 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .otel_attribute import OtelAttribute + + +class OtelSpan(pydantic_v1.BaseModel): + """ + Individual span representing a unit of work or operation + """ + + trace_id: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="traceId", default=None + ) + """ + Trace ID (16 bytes, hex-encoded string in JSON or Buffer in binary) + """ + + span_id: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="spanId", default=None + ) + """ + Span ID (8 bytes, hex-encoded string in JSON or Buffer in binary) + """ + + parent_span_id: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="parentSpanId", default=None + ) + """ + Parent span ID if this is a child span + """ + + name: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Span name describing the operation + """ + + kind: typing.Optional[int] = pydantic_v1.Field(default=None) + """ + Span kind (1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER) + """ + + start_time_unix_nano: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="startTimeUnixNano", default=None + ) + """ + Start time in nanoseconds since Unix epoch + """ + + end_time_unix_nano: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="endTimeUnixNano", default=None + ) + """ + End time in nanoseconds since Unix epoch + """ + + attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic_v1.Field( + default=None + ) + """ + Span attributes including Langfuse-specific attributes (langfuse.observation.*) + """ + + status: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) + """ + Span status object + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_trace_response.py b/langfuse/api/resources/opentelemetry/types/otel_trace_response.py new file mode 100644 index 000000000..ef9897f06 --- /dev/null +++ b/langfuse/api/resources/opentelemetry/types/otel_trace_response.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class OtelTraceResponse(pydantic_v1.BaseModel): + """ + Response from trace export request. Empty object indicates success. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/__init__.py b/langfuse/api/resources/prompts/__init__.py index 77c27486d..ea2f2f56a 100644 --- a/langfuse/api/resources/prompts/__init__.py +++ b/langfuse/api/resources/prompts/__init__.py @@ -16,6 +16,7 @@ Prompt, PromptMeta, PromptMetaListResponse, + PromptType, Prompt_Chat, Prompt_Text, TextPrompt, @@ -37,6 +38,7 @@ "Prompt", "PromptMeta", "PromptMetaListResponse", + "PromptType", "Prompt_Chat", "Prompt_Text", "TextPrompt", diff --git a/langfuse/api/resources/prompts/types/__init__.py b/langfuse/api/resources/prompts/types/__init__.py index 3067f9f04..6678ec262 100644 --- a/langfuse/api/resources/prompts/types/__init__.py +++ b/langfuse/api/resources/prompts/types/__init__.py @@ -19,6 +19,7 @@ from .prompt import Prompt, Prompt_Chat, Prompt_Text from .prompt_meta import PromptMeta from .prompt_meta_list_response import PromptMetaListResponse +from .prompt_type import PromptType from .text_prompt import TextPrompt __all__ = [ @@ -37,6 +38,7 @@ "Prompt", "PromptMeta", "PromptMetaListResponse", + "PromptType", "Prompt_Chat", "Prompt_Text", "TextPrompt", diff --git a/langfuse/api/resources/prompts/types/prompt_meta.py b/langfuse/api/resources/prompts/types/prompt_meta.py index bbb028fb2..35f8a06cf 100644 --- a/langfuse/api/resources/prompts/types/prompt_meta.py +++ b/langfuse/api/resources/prompts/types/prompt_meta.py @@ -5,10 +5,16 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .prompt_type import PromptType class PromptMeta(pydantic_v1.BaseModel): name: str + type: PromptType = pydantic_v1.Field() + """ + Indicates whether the prompt is a text or chat prompt. + """ + versions: typing.List[int] labels: typing.List[str] tags: typing.List[str] diff --git a/langfuse/api/resources/prompts/types/prompt_type.py b/langfuse/api/resources/prompts/types/prompt_type.py new file mode 100644 index 000000000..958d544a6 --- /dev/null +++ b/langfuse/api/resources/prompts/types/prompt_type.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class PromptType(str, enum.Enum): + CHAT = "chat" + TEXT = "text" + + def visit( + self, chat: typing.Callable[[], T_Result], text: typing.Callable[[], T_Result] + ) -> T_Result: + if self is PromptType.CHAT: + return chat() + if self is PromptType.TEXT: + return text() From 872387091669ecdf0e2edd9616f5249088712c46 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 21 Oct 2025 18:48:50 +0200 Subject: [PATCH 084/296] feat(api): update API spec from langfuse/langfuse 1ded68c (#1414) Co-authored-by: langfuse-bot --- langfuse/api/reference.md | 20 +++++++++++++++++++- langfuse/api/resources/metrics/client.py | 8 ++++++-- langfuse/api/resources/projects/client.py | 20 ++++++++++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 199c9421b..8291edf16 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -2963,7 +2963,9 @@ client.media.get_upload_url(
-Get metrics from the Langfuse project using a query object +Get metrics from the Langfuse project using a query object. + +For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api).
@@ -4812,6 +4814,22 @@ client.projects.create_api_key(
+**public_key:** `typing.Optional[str]` — Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + +
+
+ +
+
+ +**secret_key:** `typing.Optional[str]` — Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
diff --git a/langfuse/api/resources/metrics/client.py b/langfuse/api/resources/metrics/client.py index f46dc75f2..471f5182e 100644 --- a/langfuse/api/resources/metrics/client.py +++ b/langfuse/api/resources/metrics/client.py @@ -23,7 +23,9 @@ def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None ) -> MetricsResponse: """ - Get metrics from the Langfuse project using a query object + Get metrics from the Langfuse project using a query object. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). Parameters ---------- @@ -134,7 +136,9 @@ async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None ) -> MetricsResponse: """ - Get metrics from the Langfuse project using a query object + Get metrics from the Langfuse project using a query object. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). Parameters ---------- diff --git a/langfuse/api/resources/projects/client.py b/langfuse/api/resources/projects/client.py index 2c63e3186..9af7cfdfa 100644 --- a/langfuse/api/resources/projects/client.py +++ b/langfuse/api/resources/projects/client.py @@ -387,6 +387,8 @@ def create_api_key( project_id: str, *, note: typing.Optional[str] = OMIT, + public_key: typing.Optional[str] = OMIT, + secret_key: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ApiKeyResponse: """ @@ -399,6 +401,12 @@ def create_api_key( note : typing.Optional[str] Optional note for the API key + public_key : typing.Optional[str] + Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + + secret_key : typing.Optional[str] + Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -425,7 +433,7 @@ def create_api_key( _response = self._client_wrapper.httpx_client.request( f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", method="POST", - json={"note": note}, + json={"note": note, "publicKey": public_key, "secretKey": secret_key}, request_options=request_options, omit=OMIT, ) @@ -932,6 +940,8 @@ async def create_api_key( project_id: str, *, note: typing.Optional[str] = OMIT, + public_key: typing.Optional[str] = OMIT, + secret_key: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ApiKeyResponse: """ @@ -944,6 +954,12 @@ async def create_api_key( note : typing.Optional[str] Optional note for the API key + public_key : typing.Optional[str] + Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + + secret_key : typing.Optional[str] + Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -978,7 +994,7 @@ async def main() -> None: _response = await self._client_wrapper.httpx_client.request( f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", method="POST", - json={"note": note}, + json={"note": note, "publicKey": public_key, "secretKey": secret_key}, request_options=request_options, omit=OMIT, ) From f0619ca4bf89f058d5deea7c13651cf4fb1cdb69 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 22 Oct 2025 11:51:08 +0200 Subject: [PATCH 085/296] feat(api): update API spec from langfuse/langfuse 62d2959 (#1416) Co-authored-by: langfuse-bot --- .../annotation_queues/types/annotation_queue_object_type.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py b/langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py index 1bef33b55..6e63a7015 100644 --- a/langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py +++ b/langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py @@ -9,13 +9,17 @@ class AnnotationQueueObjectType(str, enum.Enum): TRACE = "TRACE" OBSERVATION = "OBSERVATION" + SESSION = "SESSION" def visit( self, trace: typing.Callable[[], T_Result], observation: typing.Callable[[], T_Result], + session: typing.Callable[[], T_Result], ) -> T_Result: if self is AnnotationQueueObjectType.TRACE: return trace() if self is AnnotationQueueObjectType.OBSERVATION: return observation() + if self is AnnotationQueueObjectType.SESSION: + return session() From 48ca028d1c357cf41a1375787148a29e21291787 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:35:20 +0200 Subject: [PATCH 086/296] chore(client): move to LANGFUSE_BASE_URL (#1418) --- .github/workflows/ci.yml | 2 +- langfuse/_client/client.py | 18 +- langfuse/_client/environment_variables.py | 11 +- langfuse/_client/get_client.py | 2 +- langfuse/_client/observe.py | 28 +- langfuse/_client/resource_manager.py | 18 +- langfuse/_client/span.py | 6 +- langfuse/_client/span_processor.py | 6 +- tests/api_wrapper.py | 2 +- tests/test_additional_headers_simple.py | 4 +- tests/test_core_sdk.py | 72 +++--- tests/test_deprecation.py | 5 +- tests/test_initialization.py | 299 ++++++++++++++++++++++ tests/test_otel.py | 117 ++++++--- tests/utils.py | 2 +- 15 files changed, 482 insertions(+), 110 deletions(-) create mode 100644 tests/test_initialization.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b8e0a83e..0cb4cb3d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 env: - LANGFUSE_HOST: "http://localhost:3000" + LANGFUSE_BASE_URL: "http://localhost:3000" LANGFUSE_PUBLIC_KEY: "pk-lf-1234567890" LANGFUSE_SECRET_KEY: "sk-lf-1234567890" OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 09610567d..7b9dcfcc5 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -47,6 +47,7 @@ ) from langfuse._client.datasets import DatasetClient, DatasetItemClient from langfuse._client.environment_variables import ( + LANGFUSE_BASE_URL, LANGFUSE_DEBUG, LANGFUSE_HOST, LANGFUSE_PUBLIC_KEY, @@ -134,7 +135,8 @@ class Langfuse: Parameters: public_key (Optional[str]): Your Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable. secret_key (Optional[str]): Your Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable. - host (Optional[str]): The Langfuse API host URL. Defaults to "https://cloud.langfuse.com". Can also be set via LANGFUSE_HOST environment variable. + base_url (Optional[str]): The Langfuse API base URL. Defaults to "https://cloud.langfuse.com". Can also be set via LANGFUSE_BASE_URL environment variable. + host (Optional[str]): Deprecated. Use base_url instead. The Langfuse API host URL. Defaults to "https://cloud.langfuse.com". timeout (Optional[int]): Timeout in seconds for API requests. Defaults to 5 seconds. httpx_client (Optional[httpx.Client]): Custom httpx client for making non-tracing HTTP requests. If not provided, a default client will be created. debug (bool): Enable debug logging. Defaults to False. Can also be set via LANGFUSE_DEBUG environment variable. @@ -195,6 +197,7 @@ def __init__( *, public_key: Optional[str] = None, secret_key: Optional[str] = None, + base_url: Optional[str] = None, host: Optional[str] = None, timeout: Optional[int] = None, httpx_client: Optional[httpx.Client] = None, @@ -211,7 +214,12 @@ def __init__( additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, ): - self._host = host or os.environ.get(LANGFUSE_HOST, "https://cloud.langfuse.com") + self._base_url = ( + base_url + or os.environ.get(LANGFUSE_BASE_URL) + or host + or os.environ.get(LANGFUSE_HOST, "https://cloud.langfuse.com") + ) self._environment = environment or cast( str, os.environ.get(LANGFUSE_TRACING_ENVIRONMENT) ) @@ -269,7 +277,7 @@ def __init__( self._resources = LangfuseResourceManager( public_key=public_key, secret_key=secret_key, - host=self._host, + base_url=self._base_url, timeout=timeout, environment=self._environment, release=release, @@ -2413,7 +2421,7 @@ def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]: final_trace_id = trace_id or self.get_current_trace_id() return ( - f"{self._host}/project/{project_id}/traces/{final_trace_id}" + f"{self._base_url}/project/{project_id}/traces/{final_trace_id}" if project_id and final_trace_id else None ) @@ -2712,7 +2720,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult: project_id = self._get_project_id() if project_id: - dataset_run_url = f"{self._host}/project/{project_id}/datasets/{dataset_id}/runs/{dataset_run_id}" + dataset_run_url = f"{self._base_url}/project/{project_id}/datasets/{dataset_id}/runs/{dataset_run_id}" except Exception: pass # URL generation is optional diff --git a/langfuse/_client/environment_variables.py b/langfuse/_client/environment_variables.py index d5be09d09..6b421578a 100644 --- a/langfuse/_client/environment_variables.py +++ b/langfuse/_client/environment_variables.py @@ -35,11 +35,20 @@ Secret API key of Langfuse project """ +LANGFUSE_BASE_URL = "LANGFUSE_BASE_URL" +""" +.. envvar:: LANGFUSE_BASE_URL + +Base URL of Langfuse API. Can be set via `LANGFUSE_BASE_URL` environment variable. + +**Default value:** ``"https://cloud.langfuse.com"`` +""" + LANGFUSE_HOST = "LANGFUSE_HOST" """ .. envvar:: LANGFUSE_HOST -Host of Langfuse API. Can be set via `LANGFUSE_HOST` environment variable. +Deprecated. Use LANGFUSE_BASE_URL instead. Host of Langfuse API. Can be set via `LANGFUSE_HOST` environment variable. **Default value:** ``"https://cloud.langfuse.com"`` """ diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index 8bcdecd40..402801afd 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -40,7 +40,7 @@ def _create_client_from_instance( return Langfuse( public_key=public_key or instance.public_key, secret_key=instance.secret_key, - host=instance.host, + base_url=instance.base_url, tracing_enabled=instance.tracing_enabled, environment=instance.environment, timeout=instance.timeout, diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index 4338641b7..c158c23b8 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -313,13 +313,17 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: ) # handle starlette.StreamingResponse - if type(result).__name__ == "StreamingResponse" and hasattr(result, "body_iterator"): + if type(result).__name__ == "StreamingResponse" and hasattr( + result, "body_iterator" + ): is_return_type_generator = True - result.body_iterator = self._wrap_async_generator_result( - langfuse_span_or_generation, - result.body_iterator, - transform_to_string, + result.body_iterator = ( + self._wrap_async_generator_result( + langfuse_span_or_generation, + result.body_iterator, + transform_to_string, + ) ) langfuse_span_or_generation.update(output=result) @@ -427,13 +431,17 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: ) # handle starlette.StreamingResponse - if type(result).__name__ == "StreamingResponse" and hasattr(result, "body_iterator"): + if type(result).__name__ == "StreamingResponse" and hasattr( + result, "body_iterator" + ): is_return_type_generator = True - result.body_iterator = self._wrap_async_generator_result( - langfuse_span_or_generation, - result.body_iterator, - transform_to_string, + result.body_iterator = ( + self._wrap_async_generator_result( + langfuse_span_or_generation, + result.body_iterator, + transform_to_string, + ) ) langfuse_span_or_generation.update(output=result) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 28c24e919..f0c4663d5 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -83,7 +83,7 @@ def __new__( *, public_key: str, secret_key: str, - host: str, + base_url: str, environment: Optional[str] = None, release: Optional[str] = None, timeout: Optional[int] = None, @@ -115,7 +115,7 @@ def __new__( instance._initialize_instance( public_key=public_key, secret_key=secret_key, - host=host, + base_url=base_url, timeout=timeout, environment=environment, release=release, @@ -142,7 +142,7 @@ def _initialize_instance( *, public_key: str, secret_key: str, - host: str, + base_url: str, environment: Optional[str] = None, release: Optional[str] = None, timeout: Optional[int] = None, @@ -160,7 +160,7 @@ def _initialize_instance( self.public_key = public_key self.secret_key = secret_key self.tracing_enabled = tracing_enabled - self.host = host + self.base_url = base_url self.mask = mask self.environment = environment @@ -183,7 +183,7 @@ def _initialize_instance( langfuse_processor = LangfuseSpanProcessor( public_key=self.public_key, secret_key=secret_key, - host=host, + base_url=base_url, timeout=timeout, flush_at=flush_at, flush_interval=flush_interval, @@ -212,7 +212,7 @@ def _initialize_instance( self.httpx_client = httpx.Client(timeout=timeout, headers=client_headers) self.api = FernLangfuse( - base_url=host, + base_url=base_url, username=self.public_key, password=secret_key, x_langfuse_sdk_name="python", @@ -222,7 +222,7 @@ def _initialize_instance( timeout=timeout, ) self.async_api = AsyncFernLangfuse( - base_url=host, + base_url=base_url, username=self.public_key, password=secret_key, x_langfuse_sdk_name="python", @@ -233,7 +233,7 @@ def _initialize_instance( score_ingestion_client = LangfuseClient( public_key=self.public_key, secret_key=secret_key, - base_url=host, + base_url=base_url, version=langfuse_version, timeout=timeout or 20, session=self.httpx_client, @@ -290,7 +290,7 @@ def _initialize_instance( langfuse_logger.info( f"Startup: Langfuse tracer successfully initialized | " f"public_key={self.public_key} | " - f"host={host} | " + f"base_url={base_url} | " f"environment={environment or 'default'} | " f"sample_rate={sample_rate if sample_rate is not None else 1.0} | " f"media_threads={media_upload_thread_count or 1}" diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index c078d995d..866022a6e 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -190,7 +190,9 @@ def __init__( {k: v for k, v in attributes.items() if v is not None} ) # Set OTEL span status if level is ERROR - self._set_otel_span_status_if_error(level=level, status_message=status_message) + self._set_otel_span_status_if_error( + level=level, status_message=status_message + ) def end(self, *, end_time: Optional[int] = None) -> "LangfuseObservationWrapper": """End the span, marking it as completed. @@ -544,7 +546,7 @@ def _process_media_in_attribute( return data def _set_otel_span_status_if_error( - self, *, level: Optional[SpanLevel] = None, status_message: Optional[str] = None + self, *, level: Optional[SpanLevel] = None, status_message: Optional[str] = None ) -> None: """Set OpenTelemetry span status to ERROR if level is ERROR. diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index 369d5ff9e..5a2d251c0 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -52,7 +52,7 @@ def __init__( *, public_key: str, secret_key: str, - host: str, + base_url: str, timeout: Optional[int] = None, flush_at: Optional[int] = None, flush_interval: Optional[float] = None, @@ -94,9 +94,9 @@ def __init__( traces_export_path = os.environ.get(LANGFUSE_OTEL_TRACES_EXPORT_PATH, None) endpoint = ( - f"{host}/{traces_export_path}" + f"{base_url}/{traces_export_path}" if traces_export_path - else f"{host}/api/public/otel/v1/traces" + else f"{base_url}/api/public/otel/v1/traces" ) langfuse_span_exporter = OTLPSpanExporter( diff --git a/tests/api_wrapper.py b/tests/api_wrapper.py index 42f941550..6067e6bfa 100644 --- a/tests/api_wrapper.py +++ b/tests/api_wrapper.py @@ -9,7 +9,7 @@ def __init__(self, username=None, password=None, base_url=None): username = username if username else os.environ["LANGFUSE_PUBLIC_KEY"] password = password if password else os.environ["LANGFUSE_SECRET_KEY"] self.auth = (username, password) - self.BASE_URL = base_url if base_url else os.environ["LANGFUSE_HOST"] + self.BASE_URL = base_url if base_url else os.environ["LANGFUSE_BASE_URL"] def get_observation(self, observation_id): sleep(1) diff --git a/tests/test_additional_headers_simple.py b/tests/test_additional_headers_simple.py index 8a1d07134..1f4f836bf 100644 --- a/tests/test_additional_headers_simple.py +++ b/tests/test_additional_headers_simple.py @@ -143,7 +143,7 @@ def test_span_processor_has_additional_headers_in_otel_exporter(self): processor = LangfuseSpanProcessor( public_key="test-public-key", secret_key="test-secret-key", - host="https://mock-host.com", + base_url="https://mock-host.com", additional_headers=additional_headers, ) @@ -170,7 +170,7 @@ def test_span_processor_none_additional_headers_works(self): processor = LangfuseSpanProcessor( public_key="test-public-key", secret_key="test-secret-key", - host="https://mock-host.com", + base_url="https://mock-host.com", additional_headers=None, ) diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 26d11746c..81a874ae4 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -338,7 +338,7 @@ def test_create_update_current_trace(): user_id="test", metadata={"key": "value"}, public=True, - input="test_input" + input="test_input", ) # Get trace ID for later reference trace_id = span.trace_id @@ -347,7 +347,9 @@ def test_create_update_current_trace(): sleep(1) # Update trace properties using update_current_trace - langfuse.update_current_trace(metadata={"key2": "value2"}, public=False, version="1.0") + langfuse.update_current_trace( + metadata={"key2": "value2"}, public=False, version="1.0" + ) # Ensure data is sent to the API langfuse.flush() @@ -1957,9 +1959,9 @@ def test_start_as_current_observation_types(): expected_types = {obs_type.upper() for obs_type in observation_types} | { "SPAN" } # includes parent span - assert expected_types.issubset(found_types), ( - f"Missing types: {expected_types - found_types}" - ) + assert expected_types.issubset( + found_types + ), f"Missing types: {expected_types - found_types}" # Verify each specific observation exists for obs_type in observation_types: @@ -2003,25 +2005,25 @@ def test_that_generation_like_properties_are_actually_created(): ) as obs: # Verify the properties are accessible on the observation object if hasattr(obs, "model"): - assert obs.model == test_model, ( - f"{obs_type} should have model property" - ) + assert ( + obs.model == test_model + ), f"{obs_type} should have model property" if hasattr(obs, "completion_start_time"): - assert obs.completion_start_time == test_completion_start_time, ( - f"{obs_type} should have completion_start_time property" - ) + assert ( + obs.completion_start_time == test_completion_start_time + ), f"{obs_type} should have completion_start_time property" if hasattr(obs, "model_parameters"): - assert obs.model_parameters == test_model_parameters, ( - f"{obs_type} should have model_parameters property" - ) + assert ( + obs.model_parameters == test_model_parameters + ), f"{obs_type} should have model_parameters property" if hasattr(obs, "usage_details"): - assert obs.usage_details == test_usage_details, ( - f"{obs_type} should have usage_details property" - ) + assert ( + obs.usage_details == test_usage_details + ), f"{obs_type} should have usage_details property" if hasattr(obs, "cost_details"): - assert obs.cost_details == test_cost_details, ( - f"{obs_type} should have cost_details property" - ) + assert ( + obs.cost_details == test_cost_details + ), f"{obs_type} should have cost_details property" langfuse.flush() @@ -2035,28 +2037,28 @@ def test_that_generation_like_properties_are_actually_created(): for obs in trace.observations if obs.name == f"test-{obs_type}" and obs.type == obs_type.upper() ] - assert len(observations) == 1, ( - f"Expected one {obs_type.upper()} observation, but found {len(observations)}" - ) + assert ( + len(observations) == 1 + ), f"Expected one {obs_type.upper()} observation, but found {len(observations)}" obs = observations[0] assert obs.model == test_model, f"{obs_type} should have model property" - assert obs.model_parameters == test_model_parameters, ( - f"{obs_type} should have model_parameters property" - ) + assert ( + obs.model_parameters == test_model_parameters + ), f"{obs_type} should have model_parameters property" # usage_details assert hasattr(obs, "usage_details"), f"{obs_type} should have usage_details" - assert obs.usage_details == dict(test_usage_details, total=30), ( - f"{obs_type} should persist usage_details" - ) # API adds total + assert obs.usage_details == dict( + test_usage_details, total=30 + ), f"{obs_type} should persist usage_details" # API adds total - assert obs.cost_details == test_cost_details, ( - f"{obs_type} should persist cost_details" - ) + assert ( + obs.cost_details == test_cost_details + ), f"{obs_type} should persist cost_details" # completion_start_time, because of time skew not asserting time - assert obs.completion_start_time is not None, ( - f"{obs_type} should persist completion_start_time property" - ) + assert ( + obs.completion_start_time is not None + ), f"{obs_type} should persist completion_start_time property" diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py index 9877f97d1..bcb2626b9 100644 --- a/tests/test_deprecation.py +++ b/tests/test_deprecation.py @@ -1,9 +1,10 @@ """Tests for deprecation warnings on deprecated functions.""" import warnings -import pytest from unittest.mock import patch +import pytest + from langfuse import Langfuse @@ -54,7 +55,7 @@ def langfuse_client(self): { "LANGFUSE_PUBLIC_KEY": "test_key", "LANGFUSE_SECRET_KEY": "test_secret", - "LANGFUSE_HOST": "http://localhost:3000", + "LANGFUSE_BASE_URL": "http://localhost:3000", }, ): return Langfuse() diff --git a/tests/test_initialization.py b/tests/test_initialization.py new file mode 100644 index 000000000..6664d318f --- /dev/null +++ b/tests/test_initialization.py @@ -0,0 +1,299 @@ +"""Test suite for Langfuse client initialization with LANGFUSE_HOST and LANGFUSE_BASE_URL. + +This test suite verifies that both LANGFUSE_HOST (deprecated) and LANGFUSE_BASE_URL +environment variables work correctly for initializing the Langfuse client. +""" + +import os + +import pytest + +from langfuse import Langfuse +from langfuse._client.resource_manager import LangfuseResourceManager + + +class TestClientInitialization: + """Tests for Langfuse client initialization with different URL configurations.""" + + @pytest.fixture(autouse=True) + def cleanup_env_vars(self): + """Fixture to clean up environment variables and singleton cache before and after each test.""" + # Store original values + original_values = { + "LANGFUSE_BASE_URL": os.environ.get("LANGFUSE_BASE_URL"), + "LANGFUSE_HOST": os.environ.get("LANGFUSE_HOST"), + "LANGFUSE_PUBLIC_KEY": os.environ.get("LANGFUSE_PUBLIC_KEY"), + "LANGFUSE_SECRET_KEY": os.environ.get("LANGFUSE_SECRET_KEY"), + } + + # Remove LANGFUSE_BASE_URL and LANGFUSE_HOST for the test + # but keep PUBLIC_KEY and SECRET_KEY if they exist + for key in ["LANGFUSE_BASE_URL", "LANGFUSE_HOST"]: + if key in os.environ: + del os.environ[key] + + yield + + # Clear the singleton cache to prevent test pollution + with LangfuseResourceManager._lock: + LangfuseResourceManager._instances.clear() + + # Restore original values - always remove any test values first + for key in [ + "LANGFUSE_BASE_URL", + "LANGFUSE_HOST", + "LANGFUSE_PUBLIC_KEY", + "LANGFUSE_SECRET_KEY", + ]: + if key in os.environ: + del os.environ[key] + + # Then restore original values + for key, value in original_values.items(): + if value is not None: + os.environ[key] = value + + def test_base_url_parameter_takes_precedence(self, cleanup_env_vars): + """Test that base_url parameter takes highest precedence.""" + os.environ["LANGFUSE_BASE_URL"] = "http://env-base-url.com" + os.environ["LANGFUSE_HOST"] = "http://env-host.com" + + client = Langfuse( + base_url="http://param-base-url.com", + host="http://param-host.com", + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://param-base-url.com" + + def test_env_base_url_takes_precedence_over_host_param(self, cleanup_env_vars): + """Test that LANGFUSE_BASE_URL env var takes precedence over host parameter.""" + os.environ["LANGFUSE_BASE_URL"] = "http://env-base-url.com" + + client = Langfuse( + host="http://param-host.com", + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://env-base-url.com" + + def test_host_parameter_fallback(self, cleanup_env_vars): + """Test that host parameter works as fallback when base_url is not set.""" + client = Langfuse( + host="http://param-host.com", + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://param-host.com" + + def test_env_host_fallback(self, cleanup_env_vars): + """Test that LANGFUSE_HOST env var works as fallback.""" + os.environ["LANGFUSE_HOST"] = "http://env-host.com" + + client = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://env-host.com" + + def test_default_base_url(self, cleanup_env_vars): + """Test that default base_url is used when nothing is set.""" + client = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "https://cloud.langfuse.com" + + def test_base_url_env_var(self, cleanup_env_vars): + """Test that LANGFUSE_BASE_URL environment variable is used correctly.""" + os.environ["LANGFUSE_BASE_URL"] = "http://test-base-url.com" + + client = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://test-base-url.com" + + def test_host_env_var(self, cleanup_env_vars): + """Test that LANGFUSE_HOST environment variable is used correctly (deprecated).""" + os.environ["LANGFUSE_HOST"] = "http://test-host.com" + + client = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://test-host.com" + + def test_base_url_parameter(self, cleanup_env_vars): + """Test that base_url parameter is used correctly.""" + client = Langfuse( + base_url="http://param-base-url.com", + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://param-base-url.com" + + def test_precedence_order_all_set(self, cleanup_env_vars): + """Test complete precedence order: base_url param > env > host param > env > default.""" + os.environ["LANGFUSE_BASE_URL"] = "http://env-base-url.com" + os.environ["LANGFUSE_HOST"] = "http://env-host.com" + + # Case 1: base_url parameter wins + client1 = Langfuse( + base_url="http://param-base-url.com", + host="http://param-host.com", + public_key="test_pk", + secret_key="test_sk", + ) + assert client1._base_url == "http://param-base-url.com" + + # Case 2: LANGFUSE_BASE_URL env var wins when base_url param not set + client2 = Langfuse( + host="http://param-host.com", + public_key="test_pk", + secret_key="test_sk", + ) + assert client2._base_url == "http://env-base-url.com" + + def test_precedence_without_base_url(self, cleanup_env_vars): + """Test precedence when base_url options are not set.""" + os.environ["LANGFUSE_HOST"] = "http://env-host.com" + + # Case 1: host parameter wins + client1 = Langfuse( + host="http://param-host.com", + public_key="test_pk", + secret_key="test_sk", + ) + assert client1._base_url == "http://param-host.com" + + # Case 2: LANGFUSE_HOST env var is used + client2 = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + assert client2._base_url == "http://env-host.com" + + def test_url_used_in_api_client(self, cleanup_env_vars): + """Test that the resolved base_url is correctly passed to API clients.""" + test_url = "http://test-unique-api.com" + # Use a unique public key to avoid singleton conflicts + client = Langfuse( + base_url=test_url, + public_key=f"test_pk_{test_url}", + secret_key="test_sk", + ) + + # Check that the API client has the correct base_url + assert client.api._client_wrapper._base_url == test_url + assert client.async_api._client_wrapper._base_url == test_url + + def test_url_used_in_trace_url_generation(self, cleanup_env_vars): + """Test that the resolved base_url is stored correctly for trace URL generation.""" + test_url = "http://test-trace-api.com" + # Use a unique public key to avoid singleton conflicts + client = Langfuse( + base_url=test_url, + public_key=f"test_pk_{test_url}", + secret_key="test_sk", + ) + + # Verify that the base_url is stored correctly and will be used for URL generation + # We can't test the full URL generation without making network calls to get project_id + # but we can verify the base_url is correctly set + assert client._base_url == test_url + + def test_both_base_url_and_host_params(self, cleanup_env_vars): + """Test that base_url parameter takes precedence over host parameter.""" + client = Langfuse( + base_url="http://base-url.com", + host="http://host.com", + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://base-url.com" + + def test_both_env_vars_set(self, cleanup_env_vars): + """Test that LANGFUSE_BASE_URL takes precedence over LANGFUSE_HOST.""" + os.environ["LANGFUSE_BASE_URL"] = "http://base-url.com" + os.environ["LANGFUSE_HOST"] = "http://host.com" + + client = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + + assert client._base_url == "http://base-url.com" + + def test_localhost_urls(self, cleanup_env_vars): + """Test that localhost URLs work correctly.""" + # Test with base_url + client1 = Langfuse( + base_url="http://localhost:3000", + public_key="test_pk", + secret_key="test_sk", + ) + assert client1._base_url == "http://localhost:3000" + + # Test with host (deprecated) + client2 = Langfuse( + host="http://localhost:3000", + public_key="test_pk", + secret_key="test_sk", + ) + assert client2._base_url == "http://localhost:3000" + + # Test with env var + os.environ["LANGFUSE_BASE_URL"] = "http://localhost:3000" + client3 = Langfuse( + public_key="test_pk", + secret_key="test_sk", + ) + assert client3._base_url == "http://localhost:3000" + + def test_trailing_slash_handling(self, cleanup_env_vars): + """Test that URLs with trailing slashes are handled correctly.""" + # URLs with trailing slashes should work + client1 = Langfuse( + base_url="http://test.com/", + public_key="test_pk", + secret_key="test_sk", + ) + # The SDK should accept the URL as-is (API client will handle normalization) + assert client1._base_url == "http://test.com/" + + def test_urls_with_paths(self, cleanup_env_vars): + """Test that URLs with paths work correctly.""" + client = Langfuse( + base_url="http://test.com/api/v1", + public_key="test_pk", + secret_key="test_sk", + ) + assert client._base_url == "http://test.com/api/v1" + + def test_https_and_http_urls(self, cleanup_env_vars): + """Test that both HTTPS and HTTP URLs work.""" + # HTTPS + client1 = Langfuse( + base_url="https://secure.com", + public_key="test_pk", + secret_key="test_sk", + ) + assert client1._base_url == "https://secure.com" + + # HTTP + client2 = Langfuse( + base_url="http://insecure.com", + public_key="test_pk", + secret_key="test_sk", + ) + assert client2._base_url == "http://insecure.com" diff --git a/tests/test_otel.py b/tests/test_otel.py index 623e866b5..ca87691db 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -110,7 +110,7 @@ def langfuse_client(self, monkeypatch, tracer_provider, mock_processor_init): client = Langfuse( public_key="test-public-key", secret_key="test-secret-key", - host="http://test-host", + base_url="http://test-host", tracing_enabled=True, ) @@ -134,7 +134,7 @@ def _create_client(**kwargs): client = Langfuse( public_key="test-public-key", secret_key="test-secret-key", - host="http://test-host", + base_url="http://test-host", tracing_enabled=True, **kwargs, ) @@ -950,13 +950,14 @@ def test_error_level_in_span_creation(self, langfuse_client, memory_exporter): span = langfuse_client.start_span( name="create-error-span", level="ERROR", - status_message="Initial error state" + status_message="Initial error state", ) span.end() # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() + s + for s in memory_exporter.get_finished_spans() if s.name == "create-error-span" ] assert len(raw_spans) == 1, "Expected one span" @@ -964,6 +965,7 @@ def test_error_level_in_span_creation(self, langfuse_client, memory_exporter): # Verify OTEL span status was set to ERROR from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR assert raw_span.status.description == "Initial error state" @@ -972,7 +974,10 @@ def test_error_level_in_span_creation(self, langfuse_client, memory_exporter): span_data = spans[0] attributes = span_data["attributes"] assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" - assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Initial error state" + assert ( + attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + == "Initial error state" + ) def test_error_level_in_span_update(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when updating spans to level='ERROR'.""" @@ -985,7 +990,8 @@ def test_error_level_in_span_update(self, langfuse_client, memory_exporter): # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() + s + for s in memory_exporter.get_finished_spans() if s.name == "update-error-span" ] assert len(raw_spans) == 1, "Expected one span" @@ -993,6 +999,7 @@ def test_error_level_in_span_update(self, langfuse_client, memory_exporter): # Verify OTEL span status was set to ERROR from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR assert raw_span.status.description == "Updated to error state" @@ -1001,7 +1008,10 @@ def test_error_level_in_span_update(self, langfuse_client, memory_exporter): span_data = spans[0] attributes = span_data["attributes"] assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" - assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Updated to error state" + assert ( + attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + == "Updated to error state" + ) def test_generation_error_level_in_creation(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when creating generations with level='ERROR'.""" @@ -1010,13 +1020,14 @@ def test_generation_error_level_in_creation(self, langfuse_client, memory_export name="create-error-generation", model="gpt-4", level="ERROR", - status_message="Generation failed during creation" + status_message="Generation failed during creation", ) generation.end() # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() + s + for s in memory_exporter.get_finished_spans() if s.name == "create-error-generation" ] assert len(raw_spans) == 1, "Expected one span" @@ -1024,6 +1035,7 @@ def test_generation_error_level_in_creation(self, langfuse_client, memory_export # Verify OTEL span status was set to ERROR from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR assert raw_span.status.description == "Generation failed during creation" @@ -1032,24 +1044,28 @@ def test_generation_error_level_in_creation(self, langfuse_client, memory_export span_data = spans[0] attributes = span_data["attributes"] assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" - assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Generation failed during creation" + assert ( + attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + == "Generation failed during creation" + ) def test_generation_error_level_in_update(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when updating generations to level='ERROR'.""" # Create a normal generation generation = langfuse_client.start_generation( - name="update-error-generation", - model="gpt-4", - level="INFO" + name="update-error-generation", model="gpt-4", level="INFO" ) # Update it to ERROR level - generation.update(level="ERROR", status_message="Generation failed during execution") + generation.update( + level="ERROR", status_message="Generation failed during execution" + ) generation.end() # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() + s + for s in memory_exporter.get_finished_spans() if s.name == "update-error-generation" ] assert len(raw_spans) == 1, "Expected one span" @@ -1057,6 +1073,7 @@ def test_generation_error_level_in_update(self, langfuse_client, memory_exporter # Verify OTEL span status was set to ERROR from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR assert raw_span.status.description == "Generation failed during execution" @@ -1065,9 +1082,14 @@ def test_generation_error_level_in_update(self, langfuse_client, memory_exporter span_data = spans[0] attributes = span_data["attributes"] assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" - assert attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] == "Generation failed during execution" + assert ( + attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + == "Generation failed during execution" + ) - def test_non_error_levels_dont_set_otel_status(self, langfuse_client, memory_exporter): + def test_non_error_levels_dont_set_otel_status( + self, langfuse_client, memory_exporter + ): """Test that non-ERROR levels don't set OTEL span status to ERROR.""" # Test different non-error levels test_levels = ["INFO", "WARNING", "DEBUG", None] @@ -1084,16 +1106,18 @@ def test_non_error_levels_dont_set_otel_status(self, langfuse_client, memory_exp # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() - if s.name == span_name + s for s in memory_exporter.get_finished_spans() if s.name == span_name ] assert len(raw_spans) == 1, f"Expected one span for {span_name}" raw_span = raw_spans[0] # Verify OTEL span status was NOT set to ERROR from opentelemetry.trace.status import StatusCode + # Default status should be UNSET, not ERROR - assert raw_span.status.status_code != StatusCode.ERROR, f"Level {level} should not set ERROR status" + assert ( + raw_span.status.status_code != StatusCode.ERROR + ), f"Level {level} should not set ERROR status" def test_multiple_error_updates(self, langfuse_client, memory_exporter): """Test that multiple ERROR level updates work correctly.""" @@ -1110,7 +1134,8 @@ def test_multiple_error_updates(self, langfuse_client, memory_exporter): # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() + s + for s in memory_exporter.get_finished_spans() if s.name == "multi-error-span" ] assert len(raw_spans) == 1, "Expected one span" @@ -1118,6 +1143,7 @@ def test_multiple_error_updates(self, langfuse_client, memory_exporter): # Verify OTEL span status shows the last error message from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR assert raw_span.status.description == "Second error" @@ -1129,7 +1155,8 @@ def test_error_without_status_message(self, langfuse_client, memory_exporter): # Get the raw OTEL spans to check the status raw_spans = [ - s for s in memory_exporter.get_finished_spans() + s + for s in memory_exporter.get_finished_spans() if s.name == "error-no-message-span" ] assert len(raw_spans) == 1, "Expected one span" @@ -1137,14 +1164,25 @@ def test_error_without_status_message(self, langfuse_client, memory_exporter): # Verify OTEL span status was set to ERROR even without description from opentelemetry.trace.status import StatusCode + assert raw_span.status.status_code == StatusCode.ERROR # Description should be None when no status_message provided assert raw_span.status.description is None - def test_different_observation_types_error_handling(self, langfuse_client, memory_exporter): + def test_different_observation_types_error_handling( + self, langfuse_client, memory_exporter + ): """Test that ERROR level setting works for different observation types.""" # Test different observation types - observation_types = ["agent", "tool", "chain", "retriever", "evaluator", "embedding", "guardrail"] + observation_types = [ + "agent", + "tool", + "chain", + "retriever", + "evaluator", + "embedding", + "guardrail", + ] # Create a parent span for child observations with langfuse_client.start_as_current_span(name="error-test-parent") as parent: @@ -1154,7 +1192,7 @@ def test_different_observation_types_error_handling(self, langfuse_client, memor name=f"error-{obs_type}", as_type=obs_type, level="ERROR", - status_message=f"{obs_type} failed" + status_message=f"{obs_type} failed", ) obs.end() @@ -1167,8 +1205,13 @@ def test_different_observation_types_error_handling(self, langfuse_client, memor raw_span = obs_spans[0] from opentelemetry.trace.status import StatusCode - assert raw_span.status.status_code == StatusCode.ERROR, f"{obs_type} should have ERROR status" - assert raw_span.status.description == f"{obs_type} failed", f"{obs_type} should have correct description" + + assert ( + raw_span.status.status_code == StatusCode.ERROR + ), f"{obs_type} should have ERROR status" + assert ( + raw_span.status.description == f"{obs_type} failed" + ), f"{obs_type} should have correct description" class TestAdvancedSpans(TestOTelBase): @@ -1333,7 +1376,7 @@ def test_sampling(self, monkeypatch, tracer_provider, mock_processor_init): client = Langfuse( public_key="test-public-key", secret_key="test-secret-key", - host="http://test-host", + base_url="http://test-host", tracing_enabled=True, sample_rate=0, # No sampling ) @@ -1383,7 +1426,7 @@ def test_disabled_tracing(self, monkeypatch, tracer_provider, mock_processor_ini client = Langfuse( public_key="test-public-key", secret_key="test-secret-key", - host="http://test-host", + base_url="http://test-host", tracing_enabled=False, ) @@ -1955,11 +1998,11 @@ def mock_initialize(self, **kwargs): # Initialize the two clients langfuse_project1 = Langfuse( - public_key=project1_key, secret_key="secret1", host="http://test-host" + public_key=project1_key, secret_key="secret1", base_url="http://test-host" ) langfuse_project2 = Langfuse( - public_key=project2_key, secret_key="secret2", host="http://test-host" + public_key=project2_key, secret_key="secret2", base_url="http://test-host" ) # Return the setup @@ -2313,7 +2356,7 @@ def mock_initialize(self, **kwargs): processor = LangfuseSpanProcessor( public_key=self.public_key, secret_key=self.secret_key, - host=self.host, + base_url=self.base_url, blocked_instrumentation_scopes=kwargs.get( "blocked_instrumentation_scopes" ), @@ -2358,7 +2401,7 @@ def test_blocked_instrumentation_scopes_export_filtering( Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", - host="http://localhost:3000", + base_url="http://localhost:3000", blocked_instrumentation_scopes=["openai", "anthropic"], ) @@ -2423,7 +2466,7 @@ def test_no_blocked_scopes_allows_all_exports( Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", - host="http://localhost:3000", + base_url="http://localhost:3000", blocked_instrumentation_scopes=[], ) @@ -2468,7 +2511,7 @@ def test_none_blocked_scopes_allows_all_exports( Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", - host="http://localhost:3000", + base_url="http://localhost:3000", blocked_instrumentation_scopes=None, ) @@ -2506,7 +2549,7 @@ def test_blocking_langfuse_sdk_scope_export(self, instrumentation_filtering_setu Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", - host="http://localhost:3000", + base_url="http://localhost:3000", blocked_instrumentation_scopes=["langfuse-sdk"], ) @@ -3147,7 +3190,7 @@ def langfuse_client(self, monkeypatch): client = Langfuse( public_key="test-public-key", secret_key="test-secret-key", - host="http://test-host", + base_url="http://test-host", ) return client diff --git a/tests/utils.py b/tests/utils.py index 774c0c5c3..b6aeeb185 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -22,7 +22,7 @@ def get_api(): return FernLangfuse( username=os.environ.get("LANGFUSE_PUBLIC_KEY"), password=os.environ.get("LANGFUSE_SECRET_KEY"), - base_url=os.environ.get("LANGFUSE_HOST"), + base_url=os.environ.get("LANGFUSE_BASE_URL"), ) From 52738408987134cf56d216252d59a7db6f14bdda Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:35:44 +0200 Subject: [PATCH 087/296] chore: release v3.8.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index e8019aa85..e14e05c12 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.8.0" +__version__ = "3.8.1" diff --git a/pyproject.toml b/pyproject.toml index c2aab6b1d..0725bc045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.8.0" +version = "3.8.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 3a6ff1212970e8b1a548b73e9260a20e8253c9db Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 23 Oct 2025 17:48:06 +0200 Subject: [PATCH 088/296] feat(api): update API spec from langfuse/langfuse 258cc51 (#1420) Co-authored-by: langfuse-bot --- langfuse/api/resources/comments/types/create_comment_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/api/resources/comments/types/create_comment_request.py b/langfuse/api/resources/comments/types/create_comment_request.py index 9ba6081ee..3c35c64e2 100644 --- a/langfuse/api/resources/comments/types/create_comment_request.py +++ b/langfuse/api/resources/comments/types/create_comment_request.py @@ -25,7 +25,7 @@ class CreateCommentRequest(pydantic_v1.BaseModel): content: str = pydantic_v1.Field() """ - The content of the comment. May include markdown. Currently limited to 3000 characters. + The content of the comment. May include markdown. Currently limited to 5000 characters. """ author_user_id: typing.Optional[str] = pydantic_v1.Field( From 0c11888d6f27d0491d072ded50211fec1ea1b31a Mon Sep 17 00:00:00 2001 From: Jonas Maison Date: Tue, 28 Oct 2025 10:17:13 +0100 Subject: [PATCH 089/296] fix(observe): avoid empty status message when capturing exception in observe decorator (#1417) --- langfuse/_client/observe.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index c158c23b8..afd969201 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -331,7 +331,7 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: return result except Exception as e: langfuse_span_or_generation.update( - level="ERROR", status_message=str(e) + level="ERROR", status_message=str(e) or type(e).__name__ ) raise e @@ -449,7 +449,7 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: return result except Exception as e: langfuse_span_or_generation.update( - level="ERROR", status_message=str(e) + level="ERROR", status_message=str(e) or type(e).__name__ ) raise e @@ -589,7 +589,7 @@ def __next__(self) -> Any: raise # Re-raise StopIteration except Exception as e: - self.span.update(level="ERROR", status_message=str(e)).end() + self.span.update(level="ERROR", status_message=str(e) or type(e).__name__).end() raise @@ -654,6 +654,6 @@ async def __anext__(self) -> Any: raise # Re-raise StopAsyncIteration except Exception as e: - self.span.update(level="ERROR", status_message=str(e)).end() + self.span.update(level="ERROR", status_message=str(e) or type(e).__name__).end() raise From eb66898c2ba4b5c41de418673a5e9ba7a80884c6 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:20:59 +0100 Subject: [PATCH 090/296] feat(client): propagate trace attributes onto all child spans on update (#1415) --- langfuse/__init__.py | 2 + langfuse/_client/attributes.py | 12 +- langfuse/_client/client.py | 156 +- langfuse/_client/constants.py | 4 +- langfuse/_client/propagation.py | 466 +++++ langfuse/_client/span.py | 15 +- langfuse/_client/span_processor.py | 19 +- langfuse/_client/utils.py | 5 + poetry.lock | 35 +- pyproject.toml | 1 + tests/test_experiments.py | 2 +- tests/test_propagate_attributes.py | 2771 ++++++++++++++++++++++++++++ 12 files changed, 3408 insertions(+), 80 deletions(-) create mode 100644 langfuse/_client/propagation.py create mode 100644 tests/test_propagate_attributes.py diff --git a/langfuse/__init__.py b/langfuse/__init__.py index b2b73b54b..f96b18bc8 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -7,6 +7,7 @@ from ._client.constants import ObservationTypeLiteral from ._client.get_client import get_client from ._client.observe import observe +from ._client.propagation import propagate_attributes from ._client.span import ( LangfuseAgent, LangfuseChain, @@ -26,6 +27,7 @@ "Langfuse", "get_client", "observe", + "propagate_attributes", "ObservationTypeLiteral", "LangfuseSpan", "LangfuseGeneration", diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py index 5ae81000c..343c70cdb 100644 --- a/langfuse/_client/attributes.py +++ b/langfuse/_client/attributes.py @@ -18,7 +18,6 @@ ObservationTypeGenerationLike, ObservationTypeSpanLike, ) - from langfuse._utils.serializer import EventSerializer from langfuse.model import PromptClient from langfuse.types import MapValue, SpanLevel @@ -60,6 +59,17 @@ class LangfuseOtelSpanAttributes: # Internal AS_ROOT = "langfuse.internal.as_root" + # Experiments + EXPERIMENT_ID = "langfuse.experiment.id" + EXPERIMENT_NAME = "langfuse.experiment.name" + EXPERIMENT_DESCRIPTION = "langfuse.experiment.description" + EXPERIMENT_METADATA = "langfuse.experiment.metadata" + EXPERIMENT_DATASET_ID = "langfuse.experiment.dataset.id" + EXPERIMENT_ITEM_ID = "langfuse.experiment.item.id" + EXPERIMENT_ITEM_EXPECTED_OUTPUT = "langfuse.experiment.item.expected_output" + EXPERIMENT_ITEM_METADATA = "langfuse.experiment.item.metadata" + EXPERIMENT_ITEM_ROOT_OBSERVATION_ID = "langfuse.experiment.item.root_observation_id" + def create_trace_attributes( *, diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 7b9dcfcc5..55a9667e3 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -27,7 +27,6 @@ import backoff import httpx -from opentelemetry import trace from opentelemetry import trace as otel_trace_api from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.id_generator import RandomIdGenerator @@ -37,8 +36,9 @@ ) from packaging.version import Version -from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.attributes import LangfuseOtelSpanAttributes, _serialize from langfuse._client.constants import ( + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, ObservationTypeGenerationLike, ObservationTypeLiteral, ObservationTypeLiteralNoEvent, @@ -57,6 +57,10 @@ LANGFUSE_TRACING_ENABLED, LANGFUSE_TRACING_ENVIRONMENT, ) +from langfuse._client.propagation import ( + PropagatedExperimentAttributes, + _propagate_attributes, +) from langfuse._client.resource_manager import LangfuseResourceManager from langfuse._client.span import ( LangfuseAgent, @@ -70,7 +74,7 @@ LangfuseSpan, LangfuseTool, ) -from langfuse._client.utils import run_async_safely +from langfuse._client.utils import get_sha256_hash_hex, run_async_safely from langfuse._utils import _get_timestamp from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache @@ -1638,10 +1642,6 @@ def update_current_trace( ) -> None: """Update the current trace with additional information. - This method updates the Langfuse trace that the current span belongs to. It's useful for - adding trace-level metadata like user ID, session ID, or tags that apply to - the entire Langfuse trace rather than just a single observation. - Args: name: Updated name for the Langfuse trace user_id: ID of the user who initiated the Langfuse trace @@ -1653,25 +1653,8 @@ def update_current_trace( tags: List of tags to categorize the Langfuse trace public: Whether the Langfuse trace should be publicly accessible - Example: - ```python - with langfuse.start_as_current_span(name="handle-request") as span: - # Get user information - user = authenticate_user(request) - - # Update trace with user context - langfuse.update_current_trace( - user_id=user.id, - session_id=request.session_id, - tags=["production", "web-app"] - ) - - # Continue processing - response = process_request(request) - - # Update span with results - span.update(output=response) - ``` + See Also: + :func:`langfuse.propagate_attributes`: Recommended replacement """ if not self._tracing_enabled: langfuse_logger.debug( @@ -1817,7 +1800,7 @@ def _create_remote_parent_span( is_remote=False, ) - return trace.NonRecordingSpan(span_context) + return otel_trace_api.NonRecordingSpan(span_context) def _is_valid_trace_id(self, trace_id: str) -> bool: pattern = r"^[0-9a-f]{32}$" @@ -2477,7 +2460,7 @@ def run_experiment( evaluators: List[EvaluatorFunction] = [], run_evaluators: List[RunEvaluatorFunction] = [], max_concurrency: int = 50, - metadata: Optional[Dict[str, Any]] = None, + metadata: Optional[Dict[str, str]] = None, ) -> ExperimentResult: """Run an experiment on a dataset with automatic tracing and evaluation. @@ -2649,7 +2632,7 @@ def average_accuracy(*, item_results, **kwargs): evaluators=evaluators or [], run_evaluators=run_evaluators or [], max_concurrency=max_concurrency, - metadata=metadata or {}, + metadata=metadata, ), ), ) @@ -2665,7 +2648,7 @@ async def _run_experiment_async( evaluators: List[EvaluatorFunction], run_evaluators: List[RunEvaluatorFunction], max_concurrency: int, - metadata: Dict[str, Any], + metadata: Optional[Dict[str, Any]] = None, ) -> ExperimentResult: langfuse_logger.debug( f"Starting experiment '{name}' run '{run_name}' with {len(data)} items" @@ -2763,58 +2746,51 @@ async def _process_experiment_item( experiment_name: str, experiment_run_name: str, experiment_description: Optional[str], - experiment_metadata: Dict[str, Any], + experiment_metadata: Optional[Dict[str, Any]] = None, ) -> ExperimentItemResult: - # Execute task with tracing span_name = "experiment-item-run" with self.start_as_current_span(name=span_name) as span: try: - output = await _run_task(task, item) - input_data = ( item.get("input") if isinstance(item, dict) else getattr(item, "input", None) ) - item_metadata: Dict[str, Any] = {} + if input_data is None: + raise ValueError("Experiment Item is missing input. Skipping item.") + + expected_output = ( + item.get("expected_output") + if isinstance(item, dict) + else getattr(item, "expected_output", None) + ) - if isinstance(item, dict): - item_metadata = item.get("metadata", None) or {} + item_metadata = ( + item.get("metadata") + if isinstance(item, dict) + else getattr(item, "metadata", None) + ) - final_metadata = { + final_observation_metadata = { "experiment_name": experiment_name, "experiment_run_name": experiment_run_name, - **experiment_metadata, + **(experiment_metadata or {}), } - if ( - not isinstance(item, dict) - and hasattr(item, "dataset_id") - and hasattr(item, "id") - ): - final_metadata.update( - {"dataset_id": item.dataset_id, "dataset_item_id": item.id} - ) - - if isinstance(item_metadata, dict): - final_metadata.update(item_metadata) - - span.update( - input=input_data, - output=output, - metadata=final_metadata, - ) - - # Get trace ID for linking trace_id = span.trace_id + dataset_id = None + dataset_item_id = None dataset_run_id = None # Link to dataset run if this is a dataset item if hasattr(item, "id") and hasattr(item, "dataset_id"): try: - dataset_run_item = self.api.dataset_run_items.create( + # Use sync API to avoid event loop issues when run_async_safely + # creates multiple event loops across different threads + dataset_run_item = await asyncio.to_thread( + self.api.dataset_run_items.create, request=CreateDatasetRunItemRequest( runName=experiment_run_name, runDescription=experiment_description, @@ -2822,7 +2798,7 @@ async def _process_experiment_item( datasetItemId=item.id, # type: ignore traceId=trace_id, observationId=span.id, - ) + ), ) dataset_run_id = dataset_run_item.dataset_run_id @@ -2830,18 +2806,63 @@ async def _process_experiment_item( except Exception as e: langfuse_logger.error(f"Failed to create dataset run item: {e}") + if ( + not isinstance(item, dict) + and hasattr(item, "dataset_id") + and hasattr(item, "id") + ): + dataset_id = item.dataset_id + dataset_item_id = item.id + + final_observation_metadata.update( + {"dataset_id": dataset_id, "dataset_item_id": dataset_item_id} + ) + + if isinstance(item_metadata, dict): + final_observation_metadata.update(item_metadata) + + experiment_id = dataset_run_id or self._create_observation_id() + experiment_item_id = ( + dataset_item_id or get_sha256_hash_hex(_serialize(input_data))[:16] + ) + span._otel_span.set_attributes( + { + k: v + for k, v in { + LangfuseOtelSpanAttributes.ENVIRONMENT: LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION: experiment_description, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT: _serialize( + expected_output + ), + }.items() + if v is not None + } + ) + + with _propagate_attributes( + experiment=PropagatedExperimentAttributes( + experiment_id=experiment_id, + experiment_name=experiment_run_name, + experiment_metadata=_serialize(experiment_metadata), + experiment_dataset_id=dataset_id, + experiment_item_id=experiment_item_id, + experiment_item_metadata=_serialize(item_metadata), + experiment_item_root_observation_id=span.id, + ) + ): + output = await _run_task(task, item) + + span.update( + input=input_data, + output=output, + metadata=final_observation_metadata, + ) + # Run evaluators evaluations = [] for evaluator in evaluators: try: - expected_output = None - - if isinstance(item, dict): - expected_output = item.get("expected_output") - elif hasattr(item, "expected_output"): - expected_output = item.expected_output - eval_metadata: Optional[Dict[str, Any]] = None if isinstance(item, dict): @@ -2862,6 +2883,7 @@ async def _process_experiment_item( for evaluation in eval_results: self.create_score( trace_id=trace_id, + observation_id=span.id, name=evaluation.name, value=evaluation.value, # type: ignore comment=evaluation.comment, diff --git a/langfuse/_client/constants.py b/langfuse/_client/constants.py index b699480c0..c2d0aa7aa 100644 --- a/langfuse/_client/constants.py +++ b/langfuse/_client/constants.py @@ -3,11 +3,13 @@ This module defines constants used throughout the Langfuse OpenTelemetry integration. """ -from typing import Literal, List, get_args, Union, Any +from typing import Any, List, Literal, Union, get_args + from typing_extensions import TypeAlias LANGFUSE_TRACER_NAME = "langfuse-sdk" +LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT = "sdk-experiment" """Note: this type is used with .__args__ / get_args in some cases and therefore must remain flat""" ObservationTypeGenerationLike: TypeAlias = Literal[ diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py new file mode 100644 index 000000000..49d34a99f --- /dev/null +++ b/langfuse/_client/propagation.py @@ -0,0 +1,466 @@ +"""Attribute propagation utilities for Langfuse OpenTelemetry integration. + +This module provides the `propagate_attributes` context manager for setting trace-level +attributes (user_id, session_id, metadata) that automatically propagate to all child spans +within the context. +""" + +from typing import Any, Dict, Generator, List, Literal, Optional, TypedDict, Union, cast + +from opentelemetry import baggage +from opentelemetry import ( + baggage as otel_baggage_api, +) +from opentelemetry import ( + context as otel_context_api, +) +from opentelemetry import ( + trace as otel_trace_api, +) +from opentelemetry.util._decorator import ( + _AgnosticContextManager, + _agnosticcontextmanager, +) + +from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.constants import LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT +from langfuse.logger import langfuse_logger + +PropagatedKeys = Literal[ + "user_id", + "session_id", + "metadata", + "version", + "tags", +] + +InternalPropagatedKeys = Literal[ + "experiment_id", + "experiment_name", + "experiment_metadata", + "experiment_dataset_id", + "experiment_item_id", + "experiment_item_metadata", + "experiment_item_root_observation_id", +] + +propagated_keys: List[Union[PropagatedKeys, InternalPropagatedKeys]] = [ + "user_id", + "session_id", + "metadata", + "version", + "tags", + "experiment_id", + "experiment_name", + "experiment_metadata", + "experiment_dataset_id", + "experiment_item_id", + "experiment_item_metadata", + "experiment_item_root_observation_id", +] + + +class PropagatedExperimentAttributes(TypedDict): + experiment_id: str + experiment_name: str + experiment_metadata: Optional[str] + experiment_dataset_id: Optional[str] + experiment_item_id: str + experiment_item_metadata: Optional[str] + experiment_item_root_observation_id: str + + +def propagate_attributes( + *, + user_id: Optional[str] = None, + session_id: Optional[str] = None, + metadata: Optional[Dict[str, str]] = None, + version: Optional[str] = None, + tags: Optional[List[str]] = None, + as_baggage: bool = False, +) -> _AgnosticContextManager[Any]: + """Propagate trace-level attributes to all spans created within this context. + + This context manager sets attributes on the currently active span AND automatically + propagates them to all new child spans created within the context. This is the + recommended way to set trace-level attributes like user_id, session_id, and metadata + dimensions that should be consistently applied across all observations in a trace. + + **IMPORTANT**: Call this as early as possible within your trace/workflow. Only the + currently active span and spans created after entering this context will have these + attributes. Pre-existing spans will NOT be retroactively updated. + + **Why this matters**: Langfuse aggregation queries (e.g., total cost by user_id, + filtering by session_id) only include observations that have the attribute set. + If you call `propagate_attributes` late in your workflow, earlier spans won't be + included in aggregations for that attribute. + + Args: + user_id: User identifier to associate with all spans in this context. + Must be US-ASCII string, ≤200 characters. Use this to track which user + generated each trace and enable e.g. per-user cost/performance analysis. + session_id: Session identifier to associate with all spans in this context. + Must be US-ASCII string, ≤200 characters. Use this to group related traces + within a user session (e.g., a conversation thread, multi-turn interaction). + metadata: Additional key-value metadata to propagate to all spans. + - Keys and values must be US-ASCII strings + - All values must be ≤200 characters + - Use for dimensions like internal correlating identifiers + - AVOID: large payloads, sensitive data, non-string values (will be dropped with warning) + version: Version identfier for parts of your application that are independently versioned, e.g. agents + tags: List of tags to categorize the group of observations + as_baggage: If True, propagates attributes using OpenTelemetry baggage for + cross-process/service propagation. **Security warning**: When enabled, + attribute values are added to HTTP headers on ALL outbound requests. + Only enable if values are safe to transmit via HTTP headers and you need + cross-service tracing. Default: False. + + Returns: + Context manager that propagates attributes to all child spans. + + Example: + Basic usage with user and session tracking: + + ```python + from langfuse import Langfuse + + langfuse = Langfuse() + + # Set attributes early in the trace + with langfuse.start_as_current_span(name="user_workflow") as span: + with langfuse.propagate_attributes( + user_id="user_123", + session_id="session_abc", + metadata={"experiment": "variant_a", "environment": "production"} + ): + # All spans created here will have user_id, session_id, and metadata + with langfuse.start_span(name="llm_call") as llm_span: + # This span inherits: user_id, session_id, experiment, environment + ... + + with langfuse.start_generation(name="completion") as gen: + # This span also inherits all attributes + ... + ``` + + Late propagation (anti-pattern): + + ```python + with langfuse.start_as_current_span(name="workflow") as span: + # These spans WON'T have user_id + early_span = langfuse.start_span(name="early_work") + early_span.end() + + # Set attributes in the middle + with langfuse.propagate_attributes(user_id="user_123"): + # Only spans created AFTER this point will have user_id + late_span = langfuse.start_span(name="late_work") + late_span.end() + + # Result: Aggregations by user_id will miss "early_work" span + ``` + + Cross-service propagation with baggage (advanced): + + ```python + # Service A - originating service + with langfuse.start_as_current_span(name="api_request"): + with langfuse.propagate_attributes( + user_id="user_123", + session_id="session_abc", + as_baggage=True # Propagate via HTTP headers + ): + # Make HTTP request to Service B + response = requests.get("https://service-b.example.com/api") + # user_id and session_id are now in HTTP headers + + # Service B - downstream service + # OpenTelemetry will automatically extract baggage from HTTP headers + # and propagate to spans in Service B + ``` + + Note: + - **Validation**: All attribute values (user_id, session_id, metadata values) + must be strings ≤200 characters. Invalid values will be dropped with a + warning logged. Ensure values meet constraints before calling. + - **OpenTelemetry**: This uses OpenTelemetry context propagation under the hood, + making it compatible with other OTel-instrumented libraries. + + Raises: + No exceptions are raised. Invalid values are logged as warnings and dropped. + """ + return _propagate_attributes( + user_id=user_id, + session_id=session_id, + metadata=metadata, + version=version, + tags=tags, + as_baggage=as_baggage, + ) + + +@_agnosticcontextmanager +def _propagate_attributes( + *, + user_id: Optional[str] = None, + session_id: Optional[str] = None, + metadata: Optional[Dict[str, str]] = None, + version: Optional[str] = None, + tags: Optional[List[str]] = None, + as_baggage: bool = False, + experiment: Optional[PropagatedExperimentAttributes] = None, +) -> Generator[Any, Any, Any]: + context = otel_context_api.get_current() + current_span = otel_trace_api.get_current_span() + + propagated_string_attributes: Dict[str, Optional[Union[str, List[str]]]] = { + "user_id": user_id, + "session_id": session_id, + "version": version, + "tags": tags, + } + + propagated_string_attributes = propagated_string_attributes | ( + cast(Dict[str, Union[str, List[str], None]], experiment) or {} + ) + + # Filter out None values + propagated_string_attributes = { + k: v for k, v in propagated_string_attributes.items() if v is not None + } + + for key, value in propagated_string_attributes.items(): + validated_value = _validate_propagated_value(value=value, key=key) + + if validated_value is not None: + context = _set_propagated_attribute( + key=key, + value=validated_value, + context=context, + span=current_span, + as_baggage=as_baggage, + ) + + if metadata is not None: + validated_metadata: Dict[str, str] = {} + + for key, value in metadata.items(): + if _validate_string_value(value=value, key=f"metadata.{key}"): + validated_metadata[key] = value + + if validated_metadata: + context = _set_propagated_attribute( + key="metadata", + value=validated_metadata, + context=context, + span=current_span, + as_baggage=as_baggage, + ) + + # Activate context, execute, and detach context + token = otel_context_api.attach(context=context) + + try: + yield + + finally: + otel_context_api.detach(token) + + +def _get_propagated_attributes_from_context( + context: otel_context_api.Context, +) -> Dict[str, Union[str, List[str]]]: + propagated_attributes: Dict[str, Union[str, List[str]]] = {} + + # Handle baggage + baggage_entries = baggage.get_all(context=context) + for baggage_key, baggage_value in baggage_entries.items(): + if baggage_key.startswith(LANGFUSE_BAGGAGE_PREFIX): + span_key = _get_span_key_from_baggage_key(baggage_key) + + if span_key: + propagated_attributes[span_key] = ( + baggage_value + if isinstance(baggage_value, (str, list)) + else str(baggage_value) + ) + + # Handle OTEL context + for key in propagated_keys: + context_key = _get_propagated_context_key(key) + value = otel_context_api.get_value(key=context_key, context=context) + + if value is None: + continue + + if isinstance(value, dict): + # Handle metadata + for k, v in value.items(): + span_key = f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.{k}" + propagated_attributes[span_key] = v + + else: + span_key = _get_propagated_span_key(key) + + propagated_attributes[span_key] = ( + value if isinstance(value, (str, list)) else str(value) + ) + + if ( + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID + in propagated_attributes + ): + propagated_attributes[LangfuseOtelSpanAttributes.ENVIRONMENT] = ( + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT + ) + + return propagated_attributes + + +def _set_propagated_attribute( + *, + key: str, + value: Union[str, List[str], Dict[str, str]], + context: otel_context_api.Context, + span: otel_trace_api.Span, + as_baggage: bool, +) -> otel_context_api.Context: + # Get key names + context_key = _get_propagated_context_key(key) + span_key = _get_propagated_span_key(key) + baggage_key = _get_propagated_baggage_key(key) + + # Merge metadata with previously set metadata keys + if isinstance(value, dict): + existing_metadata_in_context = cast( + dict, otel_context_api.get_value(context_key) or {} + ) + value = existing_metadata_in_context | value + + # Merge tags with previously set tags + if isinstance(value, list): + existing_tags_in_context = cast( + list, otel_context_api.get_value(context_key) or [] + ) + merged_tags = list(existing_tags_in_context) + merged_tags.extend(tag for tag in value if tag not in existing_tags_in_context) + + value = merged_tags + + # Set in context + context = otel_context_api.set_value( + key=context_key, + value=value, + context=context, + ) + + # Set on current span + if span is not None and span.is_recording(): + if isinstance(value, dict): + # Handle metadata + for k, v in value.items(): + span.set_attribute( + key=f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.{k}", + value=v, + ) + + else: + span.set_attribute(key=span_key, value=value) + + # Set on baggage + if as_baggage: + if isinstance(value, dict): + # Handle metadata + for k, v in value.items(): + context = otel_baggage_api.set_baggage( + name=f"{baggage_key}_{k}", value=v, context=context + ) + else: + context = otel_baggage_api.set_baggage( + name=baggage_key, value=value, context=context + ) + + return context + + +def _validate_propagated_value( + *, value: Any, key: str +) -> Optional[Union[str, List[str]]]: + if isinstance(value, list): + validated_values = [ + v for v in value if _validate_string_value(key=key, value=v) + ] + + return validated_values if len(validated_values) > 0 else None + + if not isinstance(value, str): + langfuse_logger.warning( # type: ignore + f"Propagated attribute '{key}' value is not a string. Dropping value." + ) + return None + + if len(value) > 200: + langfuse_logger.warning( + f"Propagated attribute '{key}' value is over 200 characters ({len(value)} chars). Dropping value." + ) + return None + + return value + + +def _validate_string_value(*, value: str, key: str) -> bool: + if not isinstance(value, str): + langfuse_logger.warning( # type: ignore + f"Propagated attribute '{key}' value is not a string. Dropping value." + ) + return False + + if len(value) > 200: + langfuse_logger.warning( + f"Propagated attribute '{key}' value is over 200 characters ({len(value)} chars). Dropping value." + ) + return False + + return True + + +def _get_propagated_context_key(key: str) -> str: + return f"langfuse.propagated.{key}" + + +LANGFUSE_BAGGAGE_PREFIX = "langfuse_" + + +def _get_propagated_baggage_key(key: str) -> str: + return f"{LANGFUSE_BAGGAGE_PREFIX}{key}" + + +def _get_span_key_from_baggage_key(key: str) -> Optional[str]: + if not key.startswith(LANGFUSE_BAGGAGE_PREFIX): + return None + + # Remove prefix to get the actual key name + suffix = key[len(LANGFUSE_BAGGAGE_PREFIX) :] + + if suffix.startswith("metadata_"): + metadata_key = suffix[len("metadata_") :] + + return _get_propagated_span_key(metadata_key) + + return _get_propagated_span_key(suffix) + + +def _get_propagated_span_key(key: str) -> str: + return { + "session_id": LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "user_id": LangfuseOtelSpanAttributes.TRACE_USER_ID, + "version": LangfuseOtelSpanAttributes.VERSION, + "tags": LangfuseOtelSpanAttributes.TRACE_TAGS, + "experiment_id": LangfuseOtelSpanAttributes.EXPERIMENT_ID, + "experiment_name": LangfuseOtelSpanAttributes.EXPERIMENT_NAME, + "experiment_metadata": LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + "experiment_dataset_id": LangfuseOtelSpanAttributes.EXPERIMENT_DATASET_ID, + "experiment_item_id": LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID, + "experiment_item_metadata": LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, + "experiment_item_root_observation_id": LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID, + }.get(key) or f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.{key}" diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 866022a6e..72ebb6bee 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -13,9 +13,9 @@ and scoring integration specific to Langfuse's observability platform. """ +import warnings from datetime import datetime from time import time_ns -import warnings from typing import ( TYPE_CHECKING, Any, @@ -30,8 +30,8 @@ ) from opentelemetry import trace as otel_trace_api -from opentelemetry.util._decorator import _AgnosticContextManager from opentelemetry.trace.status import Status, StatusCode +from opentelemetry.util._decorator import _AgnosticContextManager from langfuse.model import PromptClient @@ -45,10 +45,10 @@ create_trace_attributes, ) from langfuse._client.constants import ( - ObservationTypeLiteral, ObservationTypeGenerationLike, - ObservationTypeSpanLike, + ObservationTypeLiteral, ObservationTypeLiteralNoEvent, + ObservationTypeSpanLike, get_observation_types_list, ) from langfuse.logger import langfuse_logger @@ -223,10 +223,6 @@ def update_trace( ) -> "LangfuseObservationWrapper": """Update the trace that this span belongs to. - This method updates trace-level attributes of the trace that this span - belongs to. This is useful for adding or modifying trace-wide information - like user ID, session ID, or tags. - Args: name: Updated name for the trace user_id: ID of the user who initiated the trace @@ -237,6 +233,9 @@ def update_trace( metadata: Additional metadata to associate with the trace tags: List of tags to categorize the trace public: Whether the trace should be publicly accessible + + See Also: + :func:`langfuse.propagate_attributes`: Recommended replacement """ if not self._otel_span.is_recording(): return self diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index 5a2d251c0..a7d9fd2f4 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -15,9 +15,12 @@ import os from typing import Dict, List, Optional +from opentelemetry import context as context_api +from opentelemetry.context import Context from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter -from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.trace import ReadableSpan, Span from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.trace import format_span_id from langfuse._client.constants import LANGFUSE_TRACER_NAME from langfuse._client.environment_variables import ( @@ -25,6 +28,7 @@ LANGFUSE_FLUSH_INTERVAL, LANGFUSE_OTEL_TRACES_EXPORT_PATH, ) +from langfuse._client.propagation import _get_propagated_attributes_from_context from langfuse._client.utils import span_formatter from langfuse.logger import langfuse_logger from langfuse.version import __version__ as langfuse_version @@ -114,6 +118,19 @@ def __init__( else None, ) + def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None: + context = parent_context or context_api.get_current() + propagated_attributes = _get_propagated_attributes_from_context(context) + + if propagated_attributes: + span.set_attributes(propagated_attributes) + + langfuse_logger.debug( + f"Propagated {len(propagated_attributes)} attributes to span '{format_span_id(span.context.span_id)}': {propagated_attributes}" + ) + + return super().on_start(span, parent_context) + def on_end(self, span: ReadableSpan) -> None: # Only export spans that belong to the scoped project # This is important to not send spans to wrong project in multi-project setups diff --git a/langfuse/_client/utils.py b/langfuse/_client/utils.py index d34857ebd..16d963d88 100644 --- a/langfuse/_client/utils.py +++ b/langfuse/_client/utils.py @@ -7,6 +7,7 @@ import asyncio import json import threading +from hashlib import sha256 from typing import Any, Coroutine from opentelemetry import trace as otel_trace_api @@ -125,3 +126,7 @@ async def my_async_function(): else: # Loop exists but not running, safe to use asyncio.run() return asyncio.run(coro) + + +def get_sha256_hash_hex(value: Any) -> str: + return sha256(value.encode("utf-8")).digest().hex() diff --git a/poetry.lock b/poetry.lock index 51f812200..378d67caf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1057,6 +1057,39 @@ opentelemetry-sdk = ">=1.38.0,<1.39.0" requests = ">=2.7,<3.0" typing-extensions = ">=4.5.0" +[[package]] +name = "opentelemetry-instrumentation" +version = "0.59b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee"}, + {file = "opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +opentelemetry-semantic-conventions = "0.59b0" +packaging = ">=18.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-threading" +version = "0.59b0" +description = "Thread context propagation support for OpenTelemetry" +optional = false +python-versions = ">=3.9" +files = [ + {file = "opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca"}, + {file = "opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.59b0" +wrapt = ">=1.0.0,<2.0.0" + [[package]] name = "opentelemetry-proto" version = "1.38.0" @@ -2755,4 +2788,4 @@ cffi = ["cffi (>=1.17,<2.0)", "cffi (>=2.0.0b)"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "cfda3b10ea654d3aa01a0d4292631380b85dcaa982e726b6e6f575f15d9a22da" +content-hash = "bb4ec20d58e29f5d71599357de616571eeae016818d5c246774f0c5bc01d3d0e" diff --git a/pyproject.toml b/pyproject.toml index 0725bc045..fa262952f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ langchain-openai = ">=0.0.5,<0.4" langchain = ">=1" langgraph = ">=1" autoevals = "^0.0.130" +opentelemetry-instrumentation-threading = "^0.59b0" [tool.poetry.group.docs.dependencies] pdoc = "^15.0.4" diff --git a/tests/test_experiments.py b/tests/test_experiments.py index db3d74a65..71f2e5926 100644 --- a/tests/test_experiments.py +++ b/tests/test_experiments.py @@ -399,7 +399,7 @@ def test_dataset_with_missing_fields(): ) # Should handle missing fields gracefully - assert len(result.item_results) == 3 + assert len(result.item_results) == 2 for item_result in result.item_results: assert hasattr(item_result, "trace_id") assert hasattr(item_result, "output") diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py new file mode 100644 index 000000000..16a960c1f --- /dev/null +++ b/tests/test_propagate_attributes.py @@ -0,0 +1,2771 @@ +"""Comprehensive tests for propagate_attributes functionality. + +This module tests the propagate_attributes context manager that allows setting +trace-level attributes (user_id, session_id, metadata) that automatically propagate +to all child spans within the context. +""" + +import concurrent.futures +import time + +import pytest +from opentelemetry.instrumentation.threading import ThreadingInstrumentor + +from langfuse import propagate_attributes +from langfuse._client.attributes import LangfuseOtelSpanAttributes, _serialize +from langfuse._client.constants import LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT +from tests.test_otel import TestOTelBase + + +class TestPropagateAttributesBase(TestOTelBase): + """Base class for propagate_attributes tests with shared helper methods.""" + + @pytest.fixture + def langfuse_client(self, monkeypatch, tracer_provider, mock_processor_init): + """Create a mocked Langfuse client with explicit tracer_provider for testing.""" + from langfuse import Langfuse + + # Set environment variables + monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "test-public-key") + monkeypatch.setenv("LANGFUSE_SECRET_KEY", "test-secret-key") + + # Create test client with explicit tracer_provider + client = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + host="http://test-host", + tracing_enabled=True, + tracer_provider=tracer_provider, # Pass the test provider explicitly + ) + + yield client + + def get_span_by_name(self, memory_exporter, name: str) -> dict: + """Get single span by name (assert exactly one exists). + + Args: + memory_exporter: The in-memory span exporter fixture + name: The name of the span to retrieve + + Returns: + dict: The span data as a dictionary + + Raises: + AssertionError: If zero or more than one span with the name exists + """ + spans = self.get_spans_by_name(memory_exporter, name) + assert len(spans) == 1, f"Expected 1 span named '{name}', found {len(spans)}" + return spans[0] + + def verify_missing_attribute(self, span_data: dict, attr_key: str): + """Verify that a span does NOT have a specific attribute. + + Args: + span_data: The span data dictionary + attr_key: The attribute key to check for absence + + Raises: + AssertionError: If the attribute exists on the span + """ + attributes = span_data["attributes"] + assert ( + attr_key not in attributes + ), f"Attribute '{attr_key}' should NOT be on span '{span_data['name']}'" + + +class TestPropagateAttributesBasic(TestPropagateAttributesBase): + """Tests for basic propagate_attributes functionality.""" + + def test_user_id_propagates_to_child_spans(self, langfuse_client, memory_exporter): + """Verify user_id propagates to all child spans within context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="test_user_123"): + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + # Verify both children have user_id + child1_span = self.get_span_by_name(memory_exporter, "child-span-1") + self.verify_span_attribute( + child1_span, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "test_user_123", + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-span-2") + self.verify_span_attribute( + child2_span, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "test_user_123", + ) + + def test_session_id_propagates_to_child_spans( + self, langfuse_client, memory_exporter + ): + """Verify session_id propagates to all child spans within context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(session_id="session_abc"): + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + # Verify both children have session_id + child1_span = self.get_span_by_name(memory_exporter, "child-span-1") + self.verify_span_attribute( + child1_span, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "session_abc", + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-span-2") + self.verify_span_attribute( + child2_span, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "session_abc", + ) + + def test_metadata_propagates_to_child_spans(self, langfuse_client, memory_exporter): + """Verify metadata propagates to all child spans within context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + metadata={"experiment": "variant_a", "version": "1.0"} + ): + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + # Verify both children have metadata + child1_span = self.get_span_by_name(memory_exporter, "child-span-1") + self.verify_span_attribute( + child1_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + "variant_a", + ) + self.verify_span_attribute( + child1_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.version", + "1.0", + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-span-2") + self.verify_span_attribute( + child2_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + "variant_a", + ) + self.verify_span_attribute( + child2_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.version", + "1.0", + ) + + def test_all_attributes_propagate_together(self, langfuse_client, memory_exporter): + """Verify user_id, session_id, and metadata all propagate together.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + user_id="user_123", + session_id="session_abc", + metadata={"experiment": "test", "env": "prod"}, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has all attributes + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_abc" + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + "test", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "prod", + ) + + +class TestPropagateAttributesHierarchy(TestPropagateAttributesBase): + """Tests for propagation across span hierarchies.""" + + def test_propagation_to_direct_children(self, langfuse_client, memory_exporter): + """Verify attributes propagate to all direct children.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user_123"): + child1 = langfuse_client.start_span(name="child-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-2") + child2.end() + + child3 = langfuse_client.start_span(name="child-3") + child3.end() + + # Verify all three children have user_id + for i in range(1, 4): + child_span = self.get_span_by_name(memory_exporter, f"child-{i}") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + def test_propagation_to_grandchildren(self, langfuse_client, memory_exporter): + """Verify attributes propagate through multiple levels of nesting.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user_123", session_id="session_abc"): + with langfuse_client.start_as_current_span(name="child-span"): + grandchild = langfuse_client.start_span(name="grandchild-span") + grandchild.end() + + # Verify all three levels have attributes + parent_span = self.get_span_by_name(memory_exporter, "parent-span") + child_span = self.get_span_by_name(memory_exporter, "child-span") + grandchild_span = self.get_span_by_name(memory_exporter, "grandchild-span") + + for span in [parent_span, child_span, grandchild_span]: + self.verify_span_attribute( + span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + self.verify_span_attribute( + span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_abc" + ) + + def test_propagation_across_observation_types( + self, langfuse_client, memory_exporter + ): + """Verify attributes propagate to different observation types.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user_123"): + # Create span + span = langfuse_client.start_span(name="test-span") + span.end() + + # Create generation + generation = langfuse_client.start_observation( + as_type="generation", name="test-generation" + ) + generation.end() + + # Verify both observation types have user_id + span_data = self.get_span_by_name(memory_exporter, "test-span") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + generation_data = self.get_span_by_name(memory_exporter, "test-generation") + self.verify_span_attribute( + generation_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + +class TestPropagateAttributesTiming(TestPropagateAttributesBase): + """Critical tests for early vs late propagation timing.""" + + def test_early_propagation_all_spans_covered( + self, langfuse_client, memory_exporter + ): + """Verify setting attributes early covers all child spans.""" + with langfuse_client.start_as_current_span(name="parent-span"): + # Set attributes BEFORE creating any children + with propagate_attributes(user_id="user_123"): + child1 = langfuse_client.start_span(name="child-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-2") + child2.end() + + child3 = langfuse_client.start_span(name="child-3") + child3.end() + + # Verify ALL children have user_id + for i in range(1, 4): + child_span = self.get_span_by_name(memory_exporter, f"child-{i}") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + def test_late_propagation_only_future_spans_covered( + self, langfuse_client, memory_exporter + ): + """Verify late propagation only affects spans created after context entry.""" + with langfuse_client.start_as_current_span(name="parent-span"): + # Create child1 BEFORE propagate_attributes + child1 = langfuse_client.start_span(name="child-1") + child1.end() + + # NOW set attributes + with propagate_attributes(user_id="user_123"): + # Create child2 AFTER propagate_attributes + child2 = langfuse_client.start_span(name="child-2") + child2.end() + + # Verify: child1 does NOT have user_id, child2 DOES + child1_span = self.get_span_by_name(memory_exporter, "child-1") + self.verify_missing_attribute( + child1_span, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-2") + self.verify_span_attribute( + child2_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + def test_current_span_gets_attributes(self, langfuse_client, memory_exporter): + """Verify the currently active span gets attributes when propagate_attributes is called.""" + with langfuse_client.start_as_current_span(name="parent-span"): + # Call propagate_attributes while parent-span is active + with propagate_attributes(user_id="user_123"): + pass + + # Verify parent span itself has the attribute + parent_span = self.get_span_by_name(memory_exporter, "parent-span") + self.verify_span_attribute( + parent_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + def test_spans_outside_context_unaffected(self, langfuse_client, memory_exporter): + """Verify spans created outside context don't get attributes.""" + with langfuse_client.start_as_current_span(name="parent-span"): + # Span before context + span1 = langfuse_client.start_span(name="span-1") + span1.end() + + # Span inside context + with propagate_attributes(user_id="user_123"): + span2 = langfuse_client.start_span(name="span-2") + span2.end() + + # Span after context + span3 = langfuse_client.start_span(name="span-3") + span3.end() + + # Verify: only span2 has user_id + span1_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_missing_attribute( + span1_data, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + span2_data = self.get_span_by_name(memory_exporter, "span-2") + self.verify_span_attribute( + span2_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + span3_data = self.get_span_by_name(memory_exporter, "span-3") + self.verify_missing_attribute( + span3_data, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + +class TestPropagateAttributesValidation(TestPropagateAttributesBase): + """Tests for validation of propagated attribute values.""" + + def test_user_id_over_200_chars_dropped(self, langfuse_client, memory_exporter): + """Verify user_id over 200 characters is dropped with warning.""" + long_user_id = "x" * 201 + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id=long_user_id): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have user_id + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + def test_session_id_over_200_chars_dropped(self, langfuse_client, memory_exporter): + """Verify session_id over 200 characters is dropped with warning.""" + long_session_id = "y" * 201 + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(session_id=long_session_id): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have session_id + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID + ) + + def test_metadata_value_over_200_chars_dropped( + self, langfuse_client, memory_exporter + ): + """Verify metadata values over 200 characters are dropped with warning.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(metadata={"key": "z" * 201}): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have metadata.key + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.key" + ) + + def test_exactly_200_chars_accepted(self, langfuse_client, memory_exporter): + """Verify exactly 200 characters is accepted (boundary test).""" + user_id_200 = "x" * 200 + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id=user_id_200): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child HAS user_id + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, user_id_200 + ) + + def test_201_chars_rejected(self, langfuse_client, memory_exporter): + """Verify 201 characters is rejected (boundary test).""" + user_id_201 = "x" * 201 + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id=user_id_201): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have user_id + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + def test_non_string_user_id_dropped(self, langfuse_client, memory_exporter): + """Verify non-string user_id is dropped with warning.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id=12345): # type: ignore + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have user_id + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + def test_mixed_valid_invalid_metadata(self, langfuse_client, memory_exporter): + """Verify mixed valid/invalid metadata - valid entries kept, invalid dropped.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + metadata={ + "valid_key": "valid_value", + "invalid_key": "x" * 201, # Too long + "another_valid": "ok", + } + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify: valid keys present, invalid key absent + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.valid_key", + "valid_value", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.another_valid", + "ok", + ) + self.verify_missing_attribute( + child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.invalid_key" + ) + + +class TestPropagateAttributesNesting(TestPropagateAttributesBase): + """Tests for nested propagate_attributes contexts.""" + + def test_nested_contexts_inner_overwrites(self, langfuse_client, memory_exporter): + """Verify inner context overwrites outer context values.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user1"): + # Create span in outer context + span1 = langfuse_client.start_span(name="span-1") + span1.end() + + # Inner context with different user_id + with propagate_attributes(user_id="user2"): + span2 = langfuse_client.start_span(name="span-2") + span2.end() + + # Verify: span1 has user1, span2 has user2 + span1_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span1_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user1" + ) + + span2_data = self.get_span_by_name(memory_exporter, "span-2") + self.verify_span_attribute( + span2_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user2" + ) + + def test_after_inner_context_outer_restored(self, langfuse_client, memory_exporter): + """Verify outer context is restored after exiting inner context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user1"): + # Span in outer context + span1 = langfuse_client.start_span(name="span-1") + span1.end() + + # Inner context + with propagate_attributes(user_id="user2"): + span2 = langfuse_client.start_span(name="span-2") + span2.end() + + # Back to outer context + span3 = langfuse_client.start_span(name="span-3") + span3.end() + + # Verify: span1 and span3 have user1, span2 has user2 + span1_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span1_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user1" + ) + + span2_data = self.get_span_by_name(memory_exporter, "span-2") + self.verify_span_attribute( + span2_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user2" + ) + + span3_data = self.get_span_by_name(memory_exporter, "span-3") + self.verify_span_attribute( + span3_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user1" + ) + + def test_nested_different_attributes(self, langfuse_client, memory_exporter): + """Verify nested contexts with different attributes merge correctly.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user1"): + # Inner context adds session_id + with propagate_attributes(session_id="session1"): + span = langfuse_client.start_span(name="span-1") + span.end() + + # Verify: span has BOTH user_id and session_id + span_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user1" + ) + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session1" + ) + + def test_nested_metadata_merges_additively(self, langfuse_client, memory_exporter): + """Verify nested contexts merge metadata keys additively.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(metadata={"env": "prod", "region": "us-east"}): + # Outer span should have outer metadata + outer_span = langfuse_client.start_span(name="outer-span") + outer_span.end() + + # Inner context adds more metadata + with propagate_attributes( + metadata={"experiment": "A", "version": "2.0"} + ): + inner_span = langfuse_client.start_span(name="inner-span") + inner_span.end() + + # Back to outer context + after_span = langfuse_client.start_span(name="after-span") + after_span.end() + + # Verify: outer span has only outer metadata + outer_span_data = self.get_span_by_name(memory_exporter, "outer-span") + self.verify_span_attribute( + outer_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "prod", + ) + self.verify_span_attribute( + outer_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-east", + ) + self.verify_missing_attribute( + outer_span_data, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment" + ) + + # Verify: inner span has ALL metadata (merged) + inner_span_data = self.get_span_by_name(memory_exporter, "inner-span") + self.verify_span_attribute( + inner_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "prod", + ) + self.verify_span_attribute( + inner_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-east", + ) + self.verify_span_attribute( + inner_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + "A", + ) + self.verify_span_attribute( + inner_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.version", + "2.0", + ) + + # Verify: after span has only outer metadata (inner context exited) + after_span_data = self.get_span_by_name(memory_exporter, "after-span") + self.verify_span_attribute( + after_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "prod", + ) + self.verify_span_attribute( + after_span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-east", + ) + self.verify_missing_attribute( + after_span_data, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment" + ) + + def test_nested_metadata_inner_overwrites_conflicting_keys( + self, langfuse_client, memory_exporter + ): + """Verify nested contexts: inner metadata overwrites outer for same keys.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + metadata={"env": "staging", "version": "1.0", "region": "us-west"} + ): + # Inner context overwrites some keys + with propagate_attributes( + metadata={"env": "production", "experiment": "B"} + ): + span = langfuse_client.start_span(name="span-1") + span.end() + + # Verify: inner values overwrite outer for conflicting keys + span_data = self.get_span_by_name(memory_exporter, "span-1") + + # Overwritten key + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "production", # Inner value wins + ) + + # Preserved keys from outer + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.version", + "1.0", # From outer + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-west", # From outer + ) + + # New key from inner + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + "B", # From inner + ) + + def test_triple_nested_metadata_accumulates(self, langfuse_client, memory_exporter): + """Verify metadata accumulates across three levels of nesting.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(metadata={"level": "1", "a": "outer"}): + with propagate_attributes(metadata={"level": "2", "b": "middle"}): + with propagate_attributes(metadata={"level": "3", "c": "inner"}): + span = langfuse_client.start_span(name="deep-span") + span.end() + + # Verify: deepest span has all metadata with innermost level winning + span_data = self.get_span_by_name(memory_exporter, "deep-span") + + # Conflicting key: innermost wins + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.level", + "3", + ) + + # Unique keys from each level + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.a", + "outer", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.b", + "middle", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.c", + "inner", + ) + + def test_metadata_merge_with_empty_inner(self, langfuse_client, memory_exporter): + """Verify empty inner metadata dict doesn't clear outer metadata.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(metadata={"key1": "value1", "key2": "value2"}): + # Inner context with empty metadata + with propagate_attributes(metadata={}): + span = langfuse_client.start_span(name="span-1") + span.end() + + # Verify: outer metadata is preserved + span_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.key1", + "value1", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.key2", + "value2", + ) + + def test_metadata_merge_preserves_user_session( + self, langfuse_client, memory_exporter + ): + """Verify metadata merging doesn't affect user_id/session_id.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + user_id="user1", + session_id="session1", + metadata={"outer": "value"}, + ): + with propagate_attributes(metadata={"inner": "value"}): + span = langfuse_client.start_span(name="span-1") + span.end() + + # Verify: user_id and session_id are preserved, metadata merged + span_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user1" + ) + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session1" + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.outer", + "value", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.inner", + "value", + ) + + +class TestPropagateAttributesEdgeCases(TestPropagateAttributesBase): + """Tests for edge cases and unusual scenarios.""" + + def test_propagate_attributes_with_no_args(self, langfuse_client, memory_exporter): + """Verify calling propagate_attributes() with no args doesn't error.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Should not crash, spans created normally + child_span = self.get_span_by_name(memory_exporter, "child-span") + assert child_span is not None + + def test_none_values_ignored(self, langfuse_client, memory_exporter): + """Verify None values are ignored without error.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id=None, session_id=None, metadata=None): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Should not crash, no attributes set + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID + ) + + def test_empty_metadata_dict(self, langfuse_client, memory_exporter): + """Verify empty metadata dict doesn't cause errors.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(metadata={}): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Should not crash, no metadata attributes set + child_span = self.get_span_by_name(memory_exporter, "child-span") + assert child_span is not None + + def test_all_invalid_metadata_values(self, langfuse_client, memory_exporter): + """Verify all invalid metadata values results in no metadata attributes.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + metadata={ + "key1": "x" * 201, # Too long + "key2": "y" * 201, # Too long + } + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # No metadata attributes should be set + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute( + child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.key1" + ) + self.verify_missing_attribute( + child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.key2" + ) + + def test_propagate_with_no_active_span(self, langfuse_client, memory_exporter): + """Verify propagate_attributes works even with no active span.""" + # Call propagate_attributes without creating a parent span first + with propagate_attributes(user_id="user_123"): + # Now create a span + with langfuse_client.start_as_current_span(name="span-1"): + pass + + # Should not crash, span should have user_id + span_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + +class TestPropagateAttributesFormat(TestPropagateAttributesBase): + """Tests for correct attribute formatting and naming.""" + + def test_user_id_uses_correct_attribute_name( + self, langfuse_client, memory_exporter + ): + """Verify user_id uses the correct OTel attribute name.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(user_id="user_123"): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + # Verify the exact attribute key is used + assert LangfuseOtelSpanAttributes.TRACE_USER_ID in child_span["attributes"] + assert ( + child_span["attributes"][LangfuseOtelSpanAttributes.TRACE_USER_ID] + == "user_123" + ) + + def test_session_id_uses_correct_attribute_name( + self, langfuse_client, memory_exporter + ): + """Verify session_id uses the correct OTel attribute name.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(session_id="session_abc"): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + # Verify the exact attribute key is used + assert LangfuseOtelSpanAttributes.TRACE_SESSION_ID in child_span["attributes"] + assert ( + child_span["attributes"][LangfuseOtelSpanAttributes.TRACE_SESSION_ID] + == "session_abc" + ) + + def test_metadata_keys_properly_prefixed(self, langfuse_client, memory_exporter): + """Verify metadata keys are properly prefixed with TRACE_METADATA.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + metadata={"experiment": "A", "version": "1.0", "env": "prod"} + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + attributes = child_span["attributes"] + + # Verify each metadata key is properly prefixed + expected_keys = [ + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.version", + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + ] + + for key in expected_keys: + assert key in attributes, f"Expected key '{key}' not found in attributes" + + def test_multiple_metadata_keys_independent(self, langfuse_client, memory_exporter): + """Verify multiple metadata keys are stored as independent attributes.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(metadata={"k1": "v1", "k2": "v2", "k3": "v3"}): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + attributes = child_span["attributes"] + + # Verify all three are separate attributes with correct values + assert attributes[f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.k1"] == "v1" + assert attributes[f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.k2"] == "v2" + assert attributes[f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.k3"] == "v3" + + +class TestPropagateAttributesThreading(TestPropagateAttributesBase): + """Tests for propagate_attributes with ThreadPoolExecutor.""" + + @pytest.fixture(autouse=True) + def instrument_threading(self): + """Auto-instrument threading for all tests in this class.""" + instrumentor = ThreadingInstrumentor() + instrumentor.instrument() + yield + instrumentor.uninstrument() + + def test_propagation_with_threadpoolexecutor( + self, langfuse_client, memory_exporter + ): + """Verify attributes propagate from main thread to worker threads.""" + + def worker_function(span_name: str): + """Worker creates a span in thread pool.""" + span = langfuse_client.start_span(name=span_name) + span.end() + return span_name + + with langfuse_client.start_as_current_span(name="main-span"): + with propagate_attributes(user_id="main_user", session_id="main_session"): + # Execute work in thread pool + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + futures = [ + executor.submit(worker_function, f"worker-span-{i}") + for i in range(3) + ] + concurrent.futures.wait(futures) + + # Verify all worker spans have propagated attributes + for i in range(3): + worker_span = self.get_span_by_name(memory_exporter, f"worker-span-{i}") + self.verify_span_attribute( + worker_span, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "main_user", + ) + self.verify_span_attribute( + worker_span, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "main_session", + ) + + def test_propagation_isolated_between_threads( + self, langfuse_client, memory_exporter + ): + """Verify each thread's context is isolated from others.""" + + def create_trace_with_user(user_id: str): + """Create a trace with specific user_id.""" + with langfuse_client.start_as_current_span(name=f"trace-{user_id}"): + with propagate_attributes(user_id=user_id): + span = langfuse_client.start_span(name=f"span-{user_id}") + span.end() + + # Run two traces concurrently with different user_ids + with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: + future1 = executor.submit(create_trace_with_user, "user1") + future2 = executor.submit(create_trace_with_user, "user2") + concurrent.futures.wait([future1, future2]) + + # Verify each trace has the correct user_id (no mixing) + span1 = self.get_span_by_name(memory_exporter, "span-user1") + self.verify_span_attribute( + span1, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user1" + ) + + span2 = self.get_span_by_name(memory_exporter, "span-user2") + self.verify_span_attribute( + span2, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user2" + ) + + def test_nested_propagation_across_thread_boundary( + self, langfuse_client, memory_exporter + ): + """Verify nested spans across thread boundaries inherit attributes.""" + + def worker_creates_child(): + """Worker thread creates a child span.""" + child = langfuse_client.start_span(name="worker-child-span") + child.end() + + with langfuse_client.start_as_current_span(name="main-parent-span"): + with propagate_attributes(user_id="main_user"): + # Create span in main thread + main_child = langfuse_client.start_span(name="main-child-span") + main_child.end() + + # Create span in worker thread + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(worker_creates_child) + future.result() + + # Verify both spans (main and worker) have user_id + main_child_span = self.get_span_by_name(memory_exporter, "main-child-span") + self.verify_span_attribute( + main_child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "main_user" + ) + + worker_child_span = self.get_span_by_name(memory_exporter, "worker-child-span") + self.verify_span_attribute( + worker_child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "main_user" + ) + + def test_worker_thread_can_override_propagated_attrs( + self, langfuse_client, memory_exporter + ): + """Verify worker thread can override propagated attributes.""" + + def worker_overrides_user(): + """Worker thread sets its own user_id.""" + with propagate_attributes(user_id="worker_user"): + span = langfuse_client.start_span(name="worker-span") + span.end() + + with langfuse_client.start_as_current_span(name="main-span"): + with propagate_attributes(user_id="main_user"): + # Create span in main thread + main_span = langfuse_client.start_span(name="main-child-span") + main_span.end() + + # Worker overrides with its own user_id + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(worker_overrides_user) + future.result() + + # Verify: main span has main_user, worker span has worker_user + main_child = self.get_span_by_name(memory_exporter, "main-child-span") + self.verify_span_attribute( + main_child, LangfuseOtelSpanAttributes.TRACE_USER_ID, "main_user" + ) + + worker_span = self.get_span_by_name(memory_exporter, "worker-span") + self.verify_span_attribute( + worker_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "worker_user" + ) + + def test_multiple_workers_with_same_propagated_context( + self, langfuse_client, memory_exporter + ): + """Verify multiple workers all inherit same propagated context.""" + + def worker_function(worker_id: int): + """Worker creates a span.""" + span = langfuse_client.start_span(name=f"worker-{worker_id}") + span.end() + + with langfuse_client.start_as_current_span(name="main-span"): + with propagate_attributes(session_id="shared_session"): + # Submit 5 workers + with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: + futures = [executor.submit(worker_function, i) for i in range(5)] + concurrent.futures.wait(futures) + + # Verify all 5 workers have same session_id + for i in range(5): + worker_span = self.get_span_by_name(memory_exporter, f"worker-{i}") + self.verify_span_attribute( + worker_span, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "shared_session", + ) + + def test_concurrent_traces_with_different_attributes( + self, langfuse_client, memory_exporter + ): + """Verify concurrent traces with different attributes don't mix.""" + + def create_trace(trace_id: int): + """Create a trace with unique user_id.""" + with langfuse_client.start_as_current_span(name=f"trace-{trace_id}"): + with propagate_attributes(user_id=f"user_{trace_id}"): + span = langfuse_client.start_span(name=f"span-{trace_id}") + span.end() + + # Create 10 traces concurrently + with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: + futures = [executor.submit(create_trace, i) for i in range(10)] + concurrent.futures.wait(futures) + + # Verify each trace has its correct user_id (no mixing) + for i in range(10): + span = self.get_span_by_name(memory_exporter, f"span-{i}") + self.verify_span_attribute( + span, LangfuseOtelSpanAttributes.TRACE_USER_ID, f"user_{i}" + ) + + def test_exception_in_worker_preserves_context( + self, langfuse_client, memory_exporter + ): + """Verify exception in worker doesn't corrupt main thread context.""" + + def worker_raises_exception(): + """Worker creates span then raises exception.""" + span = langfuse_client.start_span(name="worker-span") + span.end() + raise ValueError("Test exception") + + with langfuse_client.start_as_current_span(name="main-span"): + with propagate_attributes(user_id="main_user"): + # Create span before worker + span1 = langfuse_client.start_span(name="span-before") + span1.end() + + # Worker raises exception (catch it) + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(worker_raises_exception) + try: + future.result() + except ValueError: + pass # Expected + + # Create span after exception + span2 = langfuse_client.start_span(name="span-after") + span2.end() + + # Verify both main thread spans still have correct user_id + span_before = self.get_span_by_name(memory_exporter, "span-before") + self.verify_span_attribute( + span_before, LangfuseOtelSpanAttributes.TRACE_USER_ID, "main_user" + ) + + span_after = self.get_span_by_name(memory_exporter, "span-after") + self.verify_span_attribute( + span_after, LangfuseOtelSpanAttributes.TRACE_USER_ID, "main_user" + ) + + +class TestPropagateAttributesCrossTracer(TestPropagateAttributesBase): + """Tests for propagate_attributes with different OpenTelemetry tracers.""" + + def test_different_tracer_spans_get_attributes( + self, langfuse_client, memory_exporter, tracer_provider + ): + """Verify spans from different tracers get propagated attributes.""" + # Get a different tracer (not the Langfuse tracer) + other_tracer = tracer_provider.get_tracer("other-library", "1.0.0") + + with langfuse_client.start_as_current_span(name="langfuse-parent"): + with propagate_attributes(user_id="user_123", session_id="session_abc"): + # Create span with Langfuse tracer + langfuse_span = langfuse_client.start_span(name="langfuse-child") + langfuse_span.end() + + # Create span with different tracer + with other_tracer.start_as_current_span(name="other-library-span"): + pass + + # Verify both spans have the propagated attributes + langfuse_span_data = self.get_span_by_name(memory_exporter, "langfuse-child") + self.verify_span_attribute( + langfuse_span_data, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "user_123", + ) + self.verify_span_attribute( + langfuse_span_data, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "session_abc", + ) + + other_span_data = self.get_span_by_name(memory_exporter, "other-library-span") + self.verify_span_attribute( + other_span_data, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "user_123", + ) + self.verify_span_attribute( + other_span_data, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "session_abc", + ) + + def test_nested_spans_from_multiple_tracers( + self, langfuse_client, memory_exporter, tracer_provider + ): + """Verify nested spans from multiple tracers all get propagated attributes.""" + tracer_a = tracer_provider.get_tracer("library-a", "1.0.0") + tracer_b = tracer_provider.get_tracer("library-b", "2.0.0") + + with langfuse_client.start_as_current_span(name="root"): + with propagate_attributes( + user_id="user_123", metadata={"experiment": "cross_tracer"} + ): + # Create nested spans from different tracers + with tracer_a.start_as_current_span(name="library-a-span"): + with tracer_b.start_as_current_span(name="library-b-span"): + langfuse_leaf = langfuse_client.start_span(name="langfuse-leaf") + langfuse_leaf.end() + + # Verify all spans have the attributes + for span_name in ["library-a-span", "library-b-span", "langfuse-leaf"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "user_123", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment", + "cross_tracer", + ) + + def test_other_tracer_span_before_propagate_context( + self, langfuse_client, memory_exporter, tracer_provider + ): + """Verify spans created before propagate_attributes don't get attributes.""" + other_tracer = tracer_provider.get_tracer("other-library", "1.0.0") + + with langfuse_client.start_as_current_span(name="root"): + # Create span BEFORE propagate_attributes + with other_tracer.start_as_current_span(name="span-before"): + pass + + # NOW set attributes + with propagate_attributes(user_id="user_123"): + # Create span AFTER propagate_attributes + with other_tracer.start_as_current_span(name="span-after"): + pass + + # Verify: span-before does NOT have user_id, span-after DOES + span_before = self.get_span_by_name(memory_exporter, "span-before") + self.verify_missing_attribute( + span_before, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + span_after = self.get_span_by_name(memory_exporter, "span-after") + self.verify_span_attribute( + span_after, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + def test_mixed_tracers_with_metadata( + self, langfuse_client, memory_exporter, tracer_provider + ): + """Verify metadata propagates correctly to spans from different tracers.""" + other_tracer = tracer_provider.get_tracer("instrumented-library", "1.0.0") + + with langfuse_client.start_as_current_span(name="main"): + with propagate_attributes( + metadata={ + "env": "production", + "version": "2.0", + "feature_flag": "enabled", + } + ): + # Create spans from both tracers + langfuse_span = langfuse_client.start_span(name="langfuse-operation") + langfuse_span.end() + + with other_tracer.start_as_current_span(name="library-operation"): + pass + + # Verify both spans have all metadata + for span_name in ["langfuse-operation", "library-operation"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "production", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.version", + "2.0", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.feature_flag", + "enabled", + ) + + def test_propagate_without_langfuse_parent( + self, langfuse_client, memory_exporter, tracer_provider + ): + """Verify propagate_attributes works even when parent span is from different tracer.""" + other_tracer = tracer_provider.get_tracer("other-library", "1.0.0") + + # Parent span is from different tracer + with other_tracer.start_as_current_span(name="other-parent"): + with propagate_attributes(user_id="user_123", session_id="session_xyz"): + # Create children from both tracers + with other_tracer.start_as_current_span(name="other-child"): + pass + + langfuse_child = langfuse_client.start_span(name="langfuse-child") + langfuse_child.end() + + # Verify all spans have attributes (including non-Langfuse parent) + for span_name in ["other-parent", "other-child", "langfuse-child"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "user_123", + ) + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "session_xyz", + ) + + def test_attributes_persist_across_tracer_changes( + self, langfuse_client, memory_exporter, tracer_provider + ): + """Verify attributes persist as execution moves between different tracers.""" + tracer_1 = tracer_provider.get_tracer("library-1", "1.0.0") + tracer_2 = tracer_provider.get_tracer("library-2", "1.0.0") + tracer_3 = tracer_provider.get_tracer("library-3", "1.0.0") + + with langfuse_client.start_as_current_span(name="root"): + with propagate_attributes(user_id="persistent_user"): + # Bounce between different tracers + with tracer_1.start_as_current_span(name="step-1"): + pass + + with tracer_2.start_as_current_span(name="step-2"): + with tracer_3.start_as_current_span(name="step-3"): + pass + + langfuse_span = langfuse_client.start_span(name="step-4") + langfuse_span.end() + + # Verify all steps have the user_id + for step_name in ["step-1", "step-2", "step-3", "step-4"]: + span_data = self.get_span_by_name(memory_exporter, step_name) + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "persistent_user", + ) + + +class TestPropagateAttributesAsync(TestPropagateAttributesBase): + """Tests for propagate_attributes with async/await.""" + + @pytest.mark.asyncio + async def test_async_propagation_basic(self, langfuse_client, memory_exporter): + """Verify attributes propagate in async context.""" + + async def async_operation(): + """Async function that creates a span.""" + span = langfuse_client.start_span(name="async-span") + span.end() + + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes(user_id="async_user", session_id="async_session"): + await async_operation() + + # Verify async span has attributes + async_span = self.get_span_by_name(memory_exporter, "async-span") + self.verify_span_attribute( + async_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "async_user" + ) + self.verify_span_attribute( + async_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "async_session" + ) + + @pytest.mark.asyncio + async def test_async_nested_operations(self, langfuse_client, memory_exporter): + """Verify attributes propagate through nested async operations.""" + + async def level_3(): + span = langfuse_client.start_span(name="level-3-span") + span.end() + + async def level_2(): + span = langfuse_client.start_span(name="level-2-span") + span.end() + await level_3() + + async def level_1(): + span = langfuse_client.start_span(name="level-1-span") + span.end() + await level_2() + + with langfuse_client.start_as_current_span(name="root"): + with propagate_attributes( + user_id="nested_user", metadata={"level": "nested"} + ): + await level_1() + + # Verify all levels have attributes + for span_name in ["level-1-span", "level-2-span", "level-3-span"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "nested_user" + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.level", + "nested", + ) + + @pytest.mark.asyncio + async def test_async_context_manager(self, langfuse_client, memory_exporter): + """Verify propagate_attributes works as context manager in async function.""" + with langfuse_client.start_as_current_span(name="parent"): + # propagate_attributes supports both sync and async contexts via regular 'with' + with propagate_attributes(user_id="async_ctx_user"): + span = langfuse_client.start_span(name="inside-async-ctx") + span.end() + + span_data = self.get_span_by_name(memory_exporter, "inside-async-ctx") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "async_ctx_user" + ) + + @pytest.mark.asyncio + async def test_multiple_async_tasks_concurrent( + self, langfuse_client, memory_exporter + ): + """Verify context isolation between concurrent async tasks.""" + import asyncio + + async def create_trace_with_user(user_id: str): + """Create a trace with specific user_id.""" + with langfuse_client.start_as_current_span(name=f"trace-{user_id}"): + with propagate_attributes(user_id=user_id): + await asyncio.sleep(0.01) # Simulate async work + span = langfuse_client.start_span(name=f"span-{user_id}") + span.end() + + # Run multiple traces concurrently + await asyncio.gather( + create_trace_with_user("user1"), + create_trace_with_user("user2"), + create_trace_with_user("user3"), + ) + + # Verify each trace has correct user_id (no mixing) + for user_id in ["user1", "user2", "user3"]: + span_data = self.get_span_by_name(memory_exporter, f"span-{user_id}") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, user_id + ) + + @pytest.mark.asyncio + async def test_async_with_sync_nested(self, langfuse_client, memory_exporter): + """Verify attributes propagate from async to sync code.""" + + def sync_operation(): + """Sync function called from async context.""" + span = langfuse_client.start_span(name="sync-in-async") + span.end() + + async def async_operation(): + """Async function that calls sync code.""" + span1 = langfuse_client.start_span(name="async-span") + span1.end() + sync_operation() + + with langfuse_client.start_as_current_span(name="root"): + with propagate_attributes(user_id="mixed_user"): + await async_operation() + + # Verify both spans have attributes + async_span = self.get_span_by_name(memory_exporter, "async-span") + self.verify_span_attribute( + async_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "mixed_user" + ) + + sync_span = self.get_span_by_name(memory_exporter, "sync-in-async") + self.verify_span_attribute( + sync_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "mixed_user" + ) + + @pytest.mark.asyncio + async def test_async_exception_preserves_context( + self, langfuse_client, memory_exporter + ): + """Verify context is preserved even when async operation raises exception.""" + + async def failing_operation(): + """Async operation that raises exception.""" + span = langfuse_client.start_span(name="span-before-error") + span.end() + raise ValueError("Test error") + + with langfuse_client.start_as_current_span(name="root"): + with propagate_attributes(user_id="error_user"): + span1 = langfuse_client.start_span(name="span-before-async") + span1.end() + + try: + await failing_operation() + except ValueError: + pass # Expected + + span2 = langfuse_client.start_span(name="span-after-error") + span2.end() + + # Verify all spans have attributes + for span_name in ["span-before-async", "span-before-error", "span-after-error"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "error_user" + ) + + @pytest.mark.asyncio + async def test_async_with_metadata(self, langfuse_client, memory_exporter): + """Verify metadata propagates correctly in async context.""" + + async def async_with_metadata(): + span = langfuse_client.start_span(name="async-metadata-span") + span.end() + + with langfuse_client.start_as_current_span(name="root"): + with propagate_attributes( + user_id="metadata_user", + metadata={"async": "true", "operation": "test"}, + ): + await async_with_metadata() + + span_data = self.get_span_by_name(memory_exporter, "async-metadata-span") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "metadata_user" + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.async", + "true", + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.operation", + "test", + ) + + +class TestPropagateAttributesBaggage(TestPropagateAttributesBase): + """Tests for as_baggage=True parameter and OpenTelemetry baggage propagation.""" + + def test_baggage_is_set_when_as_baggage_true(self, langfuse_client): + """Verify baggage entries are created with correct keys when as_baggage=True.""" + from opentelemetry import baggage + from opentelemetry import context as otel_context + + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes( + user_id="user_123", + session_id="session_abc", + metadata={"env": "test", "version": "2.0"}, + as_baggage=True, + ): + # Get current context and inspect baggage + current_context = otel_context.get_current() + baggage_entries = baggage.get_all(context=current_context) + + # Verify baggage entries exist with correct keys + assert "langfuse_user_id" in baggage_entries + assert baggage_entries["langfuse_user_id"] == "user_123" + + assert "langfuse_session_id" in baggage_entries + assert baggage_entries["langfuse_session_id"] == "session_abc" + + assert "langfuse_metadata_env" in baggage_entries + assert baggage_entries["langfuse_metadata_env"] == "test" + + assert "langfuse_metadata_version" in baggage_entries + assert baggage_entries["langfuse_metadata_version"] == "2.0" + + def test_spans_receive_attributes_from_baggage( + self, langfuse_client, memory_exporter + ): + """Verify child spans get attributes when parent uses as_baggage=True.""" + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes( + user_id="baggage_user", + session_id="baggage_session", + metadata={"source": "baggage"}, + as_baggage=True, + ): + # Create child span + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child span has all attributes + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "baggage_user" + ) + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "baggage_session", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.source", + "baggage", + ) + + def test_baggage_disabled_by_default(self, langfuse_client): + """Verify as_baggage=False (default) doesn't create baggage entries.""" + from opentelemetry import baggage + from opentelemetry import context as otel_context + + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes( + user_id="user_123", + session_id="session_abc", + ): + # Get current context and inspect baggage + current_context = otel_context.get_current() + baggage_entries = baggage.get_all(context=current_context) + assert len(baggage_entries) == 0 + + def test_metadata_key_with_user_id_substring_doesnt_collide( + self, langfuse_client, memory_exporter + ): + """Verify metadata key containing 'user_id' substring doesn't map to TRACE_USER_ID.""" + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes( + metadata={"user_info": "some_data", "user_id_copy": "another"}, + as_baggage=True, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + + # Should NOT have TRACE_USER_ID attribute + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID + ) + + # Should have metadata attributes with correct keys + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.user_info", + "some_data", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.user_id_copy", + "another", + ) + + def test_metadata_key_with_session_substring_doesnt_collide( + self, langfuse_client, memory_exporter + ): + """Verify metadata key containing 'session_id' substring doesn't map to TRACE_SESSION_ID.""" + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes( + metadata={"session_data": "value1", "session_id_backup": "value2"}, + as_baggage=True, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + + # Should NOT have TRACE_SESSION_ID attribute + self.verify_missing_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID + ) + + # Should have metadata attributes with correct keys + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.session_data", + "value1", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.session_id_backup", + "value2", + ) + + def test_metadata_keys_extract_correctly_from_baggage( + self, langfuse_client, memory_exporter + ): + """Verify metadata keys are correctly formatted in baggage and extracted back.""" + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes( + metadata={ + "env": "production", + "region": "us-west", + "experiment_id": "exp_123", + }, + as_baggage=True, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + + # All metadata should be under TRACE_METADATA prefix + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "production", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-west", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.experiment_id", + "exp_123", + ) + + def test_baggage_and_context_both_propagate(self, langfuse_client, memory_exporter): + """Verify attributes propagate when both baggage and context mechanisms are active.""" + with langfuse_client.start_as_current_span(name="parent"): + # Enable baggage + with propagate_attributes( + user_id="user_both", + session_id="session_both", + metadata={"source": "both"}, + as_baggage=True, + ): + # Create multiple levels of nesting + with langfuse_client.start_as_current_span(name="middle"): + child = langfuse_client.start_span(name="leaf") + child.end() + + # Verify all spans have attributes + for span_name in ["parent", "middle", "leaf"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_both" + ) + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_both" + ) + self.verify_span_attribute( + span_data, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.source", + "both", + ) + + def test_baggage_survives_context_isolation(self, langfuse_client, memory_exporter): + """Simulate cross-process scenario: baggage persists when context is detached/reattached.""" + from opentelemetry import context as otel_context + + # Step 1: Create context with baggage + with langfuse_client.start_as_current_span(name="original-process"): + with propagate_attributes( + user_id="cross_process_user", + session_id="cross_process_session", + as_baggage=True, + ): + # Capture the context with baggage + context_with_baggage = otel_context.get_current() + + # Step 2: Simulate "remote" process by creating span in saved context + # This mimics what happens when receiving an HTTP request with baggage headers + token = otel_context.attach(context_with_baggage) + try: + with langfuse_client.start_as_current_span(name="remote-process"): + child = langfuse_client.start_span(name="remote-child") + child.end() + finally: + otel_context.detach(token) + + # Verify remote spans have the propagated attributes from baggage + remote_child = self.get_span_by_name(memory_exporter, "remote-child") + self.verify_span_attribute( + remote_child, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "cross_process_user", + ) + self.verify_span_attribute( + remote_child, + LangfuseOtelSpanAttributes.TRACE_SESSION_ID, + "cross_process_session", + ) + + +class TestPropagateAttributesVersion(TestPropagateAttributesBase): + """Tests for version parameter propagation.""" + + def test_version_propagates_to_child_spans(self, langfuse_client, memory_exporter): + """Verify version propagates to all child spans within context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version="v1.2.3"): + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + # Verify both children have version + child1_span = self.get_span_by_name(memory_exporter, "child-span-1") + self.verify_span_attribute( + child1_span, + LangfuseOtelSpanAttributes.VERSION, + "v1.2.3", + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-span-2") + self.verify_span_attribute( + child2_span, + LangfuseOtelSpanAttributes.VERSION, + "v1.2.3", + ) + + def test_version_with_user_and_session(self, langfuse_client, memory_exporter): + """Verify version works together with user_id and session_id.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + user_id="user_123", + session_id="session_abc", + version="2.0.0", + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has all attributes + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_abc" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.VERSION, "2.0.0" + ) + + def test_version_with_metadata(self, langfuse_client, memory_exporter): + """Verify version works together with metadata.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + version="1.0.0", + metadata={"env": "production", "region": "us-east"}, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.VERSION, "1.0.0" + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "production", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-east", + ) + + def test_version_validation_over_200_chars(self, langfuse_client, memory_exporter): + """Verify version over 200 characters is dropped with warning.""" + long_version = "v" + "1.0.0" * 50 # Create a very long version string + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version=long_version): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have version + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.VERSION) + + def test_version_exactly_200_chars(self, langfuse_client, memory_exporter): + """Verify exactly 200 character version is accepted.""" + version_200 = "v" * 200 + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version=version_200): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child HAS version + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.VERSION, version_200 + ) + + def test_version_nested_contexts_inner_overwrites( + self, langfuse_client, memory_exporter + ): + """Verify inner context overwrites outer version.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version="1.0.0"): + # Create span in outer context + span1 = langfuse_client.start_span(name="span-1") + span1.end() + + # Inner context with different version + with propagate_attributes(version="2.0.0"): + span2 = langfuse_client.start_span(name="span-2") + span2.end() + + # Back to outer context + span3 = langfuse_client.start_span(name="span-3") + span3.end() + + # Verify: span1 and span3 have version 1.0.0, span2 has 2.0.0 + span1_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span1_data, LangfuseOtelSpanAttributes.VERSION, "1.0.0" + ) + + span2_data = self.get_span_by_name(memory_exporter, "span-2") + self.verify_span_attribute( + span2_data, LangfuseOtelSpanAttributes.VERSION, "2.0.0" + ) + + span3_data = self.get_span_by_name(memory_exporter, "span-3") + self.verify_span_attribute( + span3_data, LangfuseOtelSpanAttributes.VERSION, "1.0.0" + ) + + def test_version_with_baggage(self, langfuse_client, memory_exporter): + """Verify version propagates through baggage.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + version="baggage_version", + user_id="user_123", + as_baggage=True, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has version + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.VERSION, "baggage_version" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + + def test_version_semantic_versioning_formats( + self, langfuse_client, memory_exporter + ): + """Verify various semantic versioning formats work correctly.""" + test_versions = [ + "1.0.0", + "v2.3.4", + "1.0.0-alpha", + "2.0.0-beta.1", + "3.1.4-rc.2+build.123", + "0.1.0", + ] + + with langfuse_client.start_as_current_span(name="parent-span"): + for idx, version in enumerate(test_versions): + with propagate_attributes(version=version): + span = langfuse_client.start_span(name=f"span-{idx}") + span.end() + + # Verify all versions are correctly set + for idx, expected_version in enumerate(test_versions): + span_data = self.get_span_by_name(memory_exporter, f"span-{idx}") + self.verify_span_attribute( + span_data, LangfuseOtelSpanAttributes.VERSION, expected_version + ) + + def test_version_non_string_dropped(self, langfuse_client, memory_exporter): + """Verify non-string version is dropped with warning.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version=123): # type: ignore + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have version + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.VERSION) + + def test_version_propagates_to_grandchildren( + self, langfuse_client, memory_exporter + ): + """Verify version propagates through multiple levels of nesting.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version="nested_v1"): + with langfuse_client.start_as_current_span(name="child-span"): + grandchild = langfuse_client.start_span(name="grandchild-span") + grandchild.end() + + # Verify all three levels have version + parent_span = self.get_span_by_name(memory_exporter, "parent-span") + child_span = self.get_span_by_name(memory_exporter, "child-span") + grandchild_span = self.get_span_by_name(memory_exporter, "grandchild-span") + + for span in [parent_span, child_span, grandchild_span]: + self.verify_span_attribute( + span, LangfuseOtelSpanAttributes.VERSION, "nested_v1" + ) + + @pytest.mark.asyncio + async def test_version_with_async(self, langfuse_client, memory_exporter): + """Verify version propagates in async context.""" + + async def async_operation(): + span = langfuse_client.start_span(name="async-span") + span.end() + + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes(version="async_v1.0"): + await async_operation() + + async_span = self.get_span_by_name(memory_exporter, "async-span") + self.verify_span_attribute( + async_span, LangfuseOtelSpanAttributes.VERSION, "async_v1.0" + ) + + def test_version_attribute_key_format(self, langfuse_client, memory_exporter): + """Verify version uses correct attribute key format.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(version="key_test_v1"): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + attributes = child_span["attributes"] + + # Verify exact attribute key + assert LangfuseOtelSpanAttributes.VERSION in attributes + assert attributes[LangfuseOtelSpanAttributes.VERSION] == "key_test_v1" + + +class TestPropagateAttributesTags(TestPropagateAttributesBase): + """Tests for tags parameter propagation.""" + + def test_tags_propagate_to_child_spans(self, langfuse_client, memory_exporter): + """Verify tags propagate to all child spans within context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=["production", "api-v2", "critical"]): + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + # Verify both children have tags + child1_span = self.get_span_by_name(memory_exporter, "child-span-1") + self.verify_span_attribute( + child1_span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["production", "api-v2", "critical"]), + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-span-2") + self.verify_span_attribute( + child2_span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["production", "api-v2", "critical"]), + ) + + def test_tags_with_single_tag(self, langfuse_client, memory_exporter): + """Verify single tag works correctly.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=["experiment"]): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["experiment"]), + ) + + def test_empty_tags_list(self, langfuse_client, memory_exporter): + """Verify empty tags list is handled correctly.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=[]): + child = langfuse_client.start_span(name="child-span") + child.end() + + # With empty list, tags should not be set + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.TRACE_TAGS) + + def test_tags_with_user_and_session(self, langfuse_client, memory_exporter): + """Verify tags work together with user_id and session_id.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + user_id="user_123", + session_id="session_abc", + tags=["test", "debug"], + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has all attributes + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_abc" + ) + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["test", "debug"]), + ) + + def test_tags_with_metadata(self, langfuse_client, memory_exporter): + """Verify tags work together with metadata.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + tags=["experiment-a", "variant-1"], + metadata={"env": "staging"}, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["experiment-a", "variant-1"]), + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "staging", + ) + + def test_tags_validation_with_invalid_tag(self, langfuse_client, memory_exporter): + """Verify tags with one invalid entry drops all tags.""" + long_tag = "x" * 201 # Over 200 chars + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=["valid_tag", long_tag]): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_TAGS, tuple(["valid_tag"]) + ) + + def test_tags_nested_contexts_inner_appends(self, langfuse_client, memory_exporter): + """Verify inner context appends to outer tags.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=["outer", "tag1"]): + # Create span in outer context + span1 = langfuse_client.start_span(name="span-1") + span1.end() + + # Inner context with more tags + with propagate_attributes(tags=["inner", "tag2"]): + span2 = langfuse_client.start_span(name="span-2") + span2.end() + + # Back to outer context + span3 = langfuse_client.start_span(name="span-3") + span3.end() + + # Verify: span1 and span3 have outer tags, span2 has inner tags + span1_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span1_data, LangfuseOtelSpanAttributes.TRACE_TAGS, tuple(["outer", "tag1"]) + ) + + span2_data = self.get_span_by_name(memory_exporter, "span-2") + self.verify_span_attribute( + span2_data, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple( + [ + "outer", + "tag1", + "inner", + "tag2", + ] + ), + ) + + span3_data = self.get_span_by_name(memory_exporter, "span-3") + self.verify_span_attribute( + span3_data, LangfuseOtelSpanAttributes.TRACE_TAGS, tuple(["outer", "tag1"]) + ) + + def test_tags_with_baggage(self, langfuse_client, memory_exporter): + """Verify tags propagate through baggage.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + tags=["baggage_tag1", "baggage_tag2"], + as_baggage=True, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has tags + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["baggage_tag1", "baggage_tag2"]), + ) + + def test_tags_propagate_to_grandchildren(self, langfuse_client, memory_exporter): + """Verify tags propagate through multiple levels of nesting.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=["level1", "level2", "level3"]): + with langfuse_client.start_as_current_span(name="child-span"): + grandchild = langfuse_client.start_span(name="grandchild-span") + grandchild.end() + + # Verify all three levels have tags + parent_span = self.get_span_by_name(memory_exporter, "parent-span") + child_span = self.get_span_by_name(memory_exporter, "child-span") + grandchild_span = self.get_span_by_name(memory_exporter, "grandchild-span") + + for span in [parent_span, child_span, grandchild_span]: + self.verify_span_attribute( + span, + LangfuseOtelSpanAttributes.TRACE_TAGS, + tuple(["level1", "level2", "level3"]), + ) + + @pytest.mark.asyncio + async def test_tags_with_async(self, langfuse_client, memory_exporter): + """Verify tags propagate in async context.""" + + async def async_operation(): + span = langfuse_client.start_span(name="async-span") + span.end() + + with langfuse_client.start_as_current_span(name="parent"): + with propagate_attributes(tags=["async", "test"]): + await async_operation() + + async_span = self.get_span_by_name(memory_exporter, "async-span") + self.verify_span_attribute( + async_span, LangfuseOtelSpanAttributes.TRACE_TAGS, tuple(["async", "test"]) + ) + + def test_tags_attribute_key_format(self, langfuse_client, memory_exporter): + """Verify tags use correct attribute key format.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(tags=["key_test"]): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + attributes = child_span["attributes"] + + # Verify exact attribute key + assert LangfuseOtelSpanAttributes.TRACE_TAGS in attributes + assert attributes[LangfuseOtelSpanAttributes.TRACE_TAGS] == tuple(["key_test"]) + + +class TestPropagateAttributesExperiment(TestPropagateAttributesBase): + """Tests for experiment attribute propagation.""" + + def test_experiment_attributes_propagate_without_dataset( + self, langfuse_client, memory_exporter + ): + """Test experiment attribute propagation with local data (no Langfuse dataset).""" + # Create local dataset with metadata + local_data = [ + { + "input": "test input 1", + "expected_output": "expected result 1", + "metadata": {"item_type": "test", "priority": "high"}, + }, + ] + + # Task function that creates child spans + def task_with_child_spans(*, item, **kwargs): + # Create child spans to verify propagation + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + return f"processed: {item.get('input') if isinstance(item, dict) else item.input}" + + # Run experiment with local data + experiment_metadata = {"version": "1.0", "model": "test-model"} + result = langfuse_client.run_experiment( + name="Test Experiment", + description="Test experiment description", + data=local_data, + task=task_with_child_spans, + metadata=experiment_metadata, + ) + + # Flush to ensure spans are exported + langfuse_client.flush() + time.sleep(0.1) + + # Get the root span + root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") + assert len(root_spans) >= 1, "Should have at least 1 root span" + first_root = root_spans[0] + + # Root-only attributes should be on root + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION, + "Test experiment description", + ) + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT, + _serialize("expected result 1"), + ) + + # Propagated attributes should also be on root + experiment_id = first_root["attributes"][ + LangfuseOtelSpanAttributes.EXPERIMENT_ID + ] + experiment_item_id = first_root["attributes"][ + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID + ] + root_observation_id = first_root["attributes"][ + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID + ] + + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_NAME, + result.run_name, + ) + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + _serialize(experiment_metadata), + ) + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, + _serialize({"item_type": "test", "priority": "high"}), + ) + + # Environment should be set to sdk-experiment + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Dataset ID should not be set for local data + self.verify_missing_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_DATASET_ID, + ) + + # Verify child spans have propagated attributes but NOT root-only attributes + child_spans = self.get_spans_by_name( + memory_exporter, "child-span-1" + ) + self.get_spans_by_name(memory_exporter, "child-span-2") + + assert len(child_spans) >= 2, "Should have at least 2 child spans" + + for child_span in child_spans[:2]: # Check first item's children + # Propagated attributes should be present + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ID, + experiment_id, + ) + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_NAME, + result.run_name, + ) + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + _serialize(experiment_metadata), + ) + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID, + experiment_item_id, + ) + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID, + root_observation_id, + ) + + # Environment should be propagated to children + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Root-only attributes should NOT be present on children + self.verify_missing_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION, + ) + self.verify_missing_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT, + ) + + # Dataset ID should not be set for local data + self.verify_missing_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_DATASET_ID, + ) + + def test_experiment_attributes_propagate_with_dataset( + self, langfuse_client, memory_exporter, monkeypatch + ): + """Test experiment attribute propagation with Langfuse dataset.""" + import time + from datetime import datetime + + from langfuse._client.attributes import _serialize + from langfuse._client.datasets import DatasetClient, DatasetItemClient + from langfuse.model import Dataset, DatasetItem, DatasetStatus + + # Mock the async API to create dataset run items + async def mock_create_dataset_run_item(*args, **kwargs): + from langfuse.api.resources.dataset_run_items.types import DatasetRunItem + + request = kwargs.get("request") + return DatasetRunItem( + id="mock-run-item-id", + dataset_run_id="mock-dataset-run-id-123", + dataset_item_id=request.datasetItemId if request else "mock-item-id", + trace_id="mock-trace-id", + ) + + monkeypatch.setattr( + langfuse_client.async_api.dataset_run_items, + "create", + mock_create_dataset_run_item, + ) + + # Create a mock dataset with items + dataset_id = "test-dataset-id-456" + dataset_item_id = "test-dataset-item-id-789" + + mock_dataset = Dataset( + id=dataset_id, + name="Test Dataset", + description="Test dataset description", + project_id="test-project-id", + metadata={"test": "metadata"}, + created_at=datetime.now(), + updated_at=datetime.now(), + ) + + mock_dataset_item = DatasetItem( + id=dataset_item_id, + status=DatasetStatus.ACTIVE, + input="Germany", + expected_output="Berlin", + metadata={"source": "dataset", "index": 0}, + source_trace_id=None, + source_observation_id=None, + dataset_id=dataset_id, + dataset_name="Test Dataset", + created_at=datetime.now(), + updated_at=datetime.now(), + ) + + # Create dataset client with items + dataset_item_client = DatasetItemClient(mock_dataset_item, langfuse_client) + dataset = DatasetClient(mock_dataset, [dataset_item_client]) + + # Task with child spans + def task_with_children(*, item, **kwargs): + child1 = langfuse_client.start_span(name="dataset-child-1") + child1.end() + + child2 = langfuse_client.start_span(name="dataset-child-2") + child2.end() + + return f"Capital: {item.expected_output}" + + # Run experiment + experiment_metadata = {"dataset_version": "v2", "test_run": "true"} + dataset.run_experiment( + name="Dataset Test", + description="Dataset experiment description", + task=task_with_children, + metadata=experiment_metadata, + ) + + langfuse_client.flush() + time.sleep(0.1) + + # Verify root has dataset-specific attributes + root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") + assert len(root_spans) >= 1, "Should have at least 1 root span" + first_root = root_spans[0] + + # Root-only attributes should be on root + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION, + "Dataset experiment description", + ) + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT, + _serialize("Berlin"), + ) + + # Should have dataset ID (this is the key difference from local data) + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_DATASET_ID, + dataset_id, + ) + + # Should have the dataset item ID + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID, + dataset_item_id, + ) + + # Should have experiment metadata + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + _serialize(experiment_metadata), + ) + + # Environment should be set to sdk-experiment + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Verify child spans have dataset-specific propagated attributes + child_spans = self.get_spans_by_name( + memory_exporter, "dataset-child-1" + ) + self.get_spans_by_name(memory_exporter, "dataset-child-2") + + assert len(child_spans) >= 2, "Should have at least 2 child spans" + + for child_span in child_spans[:2]: + # Dataset ID should be propagated to children + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_DATASET_ID, + dataset_id, + ) + + # Dataset item ID should be propagated + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID, + dataset_item_id, + ) + + # Experiment metadata should be propagated + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + _serialize(experiment_metadata), + ) + + # Item metadata should be propagated + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, + _serialize({"source": "dataset", "index": 0}), + ) + + # Environment should be propagated to children + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Root-only attributes should NOT be present on children + self.verify_missing_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION, + ) + self.verify_missing_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT, + ) + + def test_experiment_attributes_propagate_to_nested_children( + self, langfuse_client, memory_exporter + ): + """Test experiment attributes propagate to deeply nested child spans.""" + local_data = [{"input": "test", "expected_output": "result"}] + + # Task with deeply nested spans + def task_with_nested_spans(*, item, **kwargs): + with langfuse_client.start_as_current_span(name="child-span"): + with langfuse_client.start_as_current_span(name="grandchild-span"): + great_grandchild = langfuse_client.start_span( + name="great-grandchild-span" + ) + great_grandchild.end() + + return "processed" + + result = langfuse_client.run_experiment( + name="Nested Test", + description="Nested test", + data=local_data, + task=task_with_nested_spans, + metadata={"depth": "test"}, + ) + + langfuse_client.flush() + time.sleep(0.1) + + root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") + first_root = root_spans[0] + experiment_id = first_root["attributes"][ + LangfuseOtelSpanAttributes.EXPERIMENT_ID + ] + root_observation_id = first_root["attributes"][ + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID + ] + + # Verify root has environment set + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Verify all nested children have propagated attributes + for span_name in ["child-span", "grandchild-span", "great-grandchild-span"]: + span_data = self.get_span_by_name(memory_exporter, span_name) + + # Propagated attributes should be present + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.EXPERIMENT_ID, + experiment_id, + ) + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.EXPERIMENT_NAME, + result.run_name, + ) + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID, + root_observation_id, + ) + + # Environment should be propagated to all nested children + self.verify_span_attribute( + span_data, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Root-only attributes should NOT be present + self.verify_missing_attribute( + span_data, + LangfuseOtelSpanAttributes.EXPERIMENT_DESCRIPTION, + ) + self.verify_missing_attribute( + span_data, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_EXPECTED_OUTPUT, + ) + + def test_experiment_metadata_merging(self, langfuse_client, memory_exporter): + """Test that experiment metadata and item metadata are both propagated correctly.""" + import time + + from langfuse._client.attributes import _serialize + + # Rich metadata + experiment_metadata = { + "experiment_type": "A/B test", + "model_version": "2.0", + "temperature": 0.7, + } + item_metadata = { + "item_category": "finance", + "difficulty": "hard", + "language": "en", + } + + local_data = [ + { + "input": "test", + "expected_output": {"status": "success"}, + "metadata": item_metadata, + } + ] + + def task_with_child(*, item, **kwargs): + child = langfuse_client.start_span(name="metadata-child") + child.end() + return "result" + + langfuse_client.run_experiment( + name="Metadata Test", + description="Metadata test", + data=local_data, + task=task_with_child, + metadata=experiment_metadata, + ) + + langfuse_client.flush() + time.sleep(0.1) + + # Verify root span has environment set + root_span = self.get_span_by_name(memory_exporter, "experiment-item-run") + self.verify_span_attribute( + root_span, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) + + # Verify child span has both experiment and item metadata propagated + child_span = self.get_span_by_name(memory_exporter, "metadata-child") + + # Verify experiment metadata is serialized and propagated + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + _serialize(experiment_metadata), + ) + + # Verify item metadata is serialized and propagated + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, + _serialize(item_metadata), + ) + + # Verify environment is propagated to child + self.verify_span_attribute( + child_span, + LangfuseOtelSpanAttributes.ENVIRONMENT, + LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, + ) From fbac841319c3e0470027c6702e4cd2fff93de60b Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:25:41 +0100 Subject: [PATCH 091/296] chore: release v3.9.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index e14e05c12..96bad3013 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.8.1" +__version__ = "3.9.0" diff --git a/pyproject.toml b/pyproject.toml index fa262952f..a118c3127 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.8.1" +version = "3.9.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 99e081d5487da330aeac55947920c6b7611aabca Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 3 Nov 2025 11:27:56 +0100 Subject: [PATCH 092/296] feat(api): update API spec from langfuse/langfuse efe3532 (#1425) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 4 + langfuse/api/reference.md | 229 +++++++++++++++++- langfuse/api/resources/__init__.py | 4 + langfuse/api/resources/observations/client.py | 156 +++++++++++- .../api/resources/organizations/__init__.py | 4 + .../api/resources/organizations/client.py | 133 ++++++++++ .../resources/organizations/types/__init__.py | 4 + .../types/organization_api_key.py | 54 +++++ .../types/organization_api_keys_response.py | 45 ++++ langfuse/api/resources/trace/client.py | 172 ++++++++++++- 10 files changed, 793 insertions(+), 12 deletions(-) create mode 100644 langfuse/api/resources/organizations/types/organization_api_key.py create mode 100644 langfuse/api/resources/organizations/types/organization_api_keys_response.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index ce613a95d..451ad991e 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -134,6 +134,8 @@ OpenAiResponseUsageSchema, OpenAiUsage, OptionalObservationBody, + OrganizationApiKey, + OrganizationApiKeysResponse, OrganizationProject, OrganizationProjectsResponse, OtelAttribute, @@ -378,6 +380,8 @@ "OpenAiResponseUsageSchema", "OpenAiUsage", "OptionalObservationBody", + "OrganizationApiKey", + "OrganizationApiKeysResponse", "OrganizationProject", "OrganizationProjectsResponse", "OtelAttribute", diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 8291edf16..79be952da 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -3608,13 +3608,15 @@ client.observations.get_many() **filter:** `typing.Optional[str]` -JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). +JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + +## Filter Structure Each filter condition has the following structure: ```json [ { "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on + "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" // - string: "=", "contains", "does not contain", "starts with", "ends with" @@ -3631,6 +3633,78 @@ Each filter condition has the following structure: } ] ``` + +## Available Columns + +### Core Observation Fields +- `id` (string) - Observation ID +- `type` (string) - Observation type (SPAN, GENERATION, EVENT) +- `name` (string) - Observation name +- `traceId` (string) - Associated trace ID +- `startTime` (datetime) - Observation start time +- `endTime` (datetime) - Observation end time +- `environment` (string) - Environment tag +- `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) +- `statusMessage` (string) - Status message +- `version` (string) - Version tag + +### Performance Metrics +- `latency` (number) - Latency in seconds (calculated: end_time - start_time) +- `timeToFirstToken` (number) - Time to first token in seconds +- `tokensPerSecond` (number) - Output tokens per second + +### Token Usage +- `inputTokens` (number) - Number of input tokens +- `outputTokens` (number) - Number of output tokens +- `totalTokens` (number) - Total tokens (alias: `tokens`) + +### Cost Metrics +- `inputCost` (number) - Input cost in USD +- `outputCost` (number) - Output cost in USD +- `totalCost` (number) - Total cost in USD + +### Model Information +- `model` (string) - Provided model name +- `promptName` (string) - Associated prompt name +- `promptVersion` (number) - Associated prompt version + +### Structured Data +- `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + +### Scores (requires join with scores table) +- `scores_avg` (number) - Average of numeric scores (alias: `scores`) +- `score_categories` (categoryOptions) - Categorical score values + +### Associated Trace Fields (requires join with traces table) +- `userId` (string) - User ID from associated trace +- `traceName` (string) - Name from associated trace +- `traceEnvironment` (string) - Environment from associated trace +- `traceTags` (arrayOptions) - Tags from associated trace + +## Filter Examples +```json +[ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } +] +```
@@ -4330,6 +4404,71 @@ client.organizations.get_organization_projects() + + +
+ +
client.organizations.get_organization_api_keys() +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get all API keys for the organization associated with the API key (requires organization-scoped API key) +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.organizations.get_organization_api_keys() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -7104,13 +7243,15 @@ client.trace.list() **filter:** `typing.Optional[str]` -JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). +JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + +## Filter Structure Each filter condition has the following structure: ```json [ { "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on + "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" // - string: "=", "contains", "does not contain", "starts with", "ends with" @@ -7127,6 +7268,86 @@ Each filter condition has the following structure: } ] ``` + +## Available Columns + +### Core Trace Fields +- `id` (string) - Trace ID +- `name` (string) - Trace name +- `timestamp` (datetime) - Trace timestamp +- `userId` (string) - User ID +- `sessionId` (string) - Session ID +- `environment` (string) - Environment tag +- `version` (string) - Version tag +- `release` (string) - Release tag +- `tags` (arrayOptions) - Array of tags +- `bookmarked` (boolean) - Bookmark status + +### Structured Data +- `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + +### Aggregated Metrics (from observations) +These metrics are aggregated from all observations within the trace: +- `latency` (number) - Latency in seconds (time from first observation start to last observation end) +- `inputTokens` (number) - Total input tokens across all observations +- `outputTokens` (number) - Total output tokens across all observations +- `totalTokens` (number) - Total tokens (alias: `tokens`) +- `inputCost` (number) - Total input cost in USD +- `outputCost` (number) - Total output cost in USD +- `totalCost` (number) - Total cost in USD + +### Observation Level Aggregations +These fields aggregate observation levels within the trace: +- `level` (string) - Highest severity level (ERROR > WARNING > DEFAULT > DEBUG) +- `warningCount` (number) - Count of WARNING level observations +- `errorCount` (number) - Count of ERROR level observations +- `defaultCount` (number) - Count of DEFAULT level observations +- `debugCount` (number) - Count of DEBUG level observations + +### Scores (requires join with scores table) +- `scores_avg` (number) - Average of numeric scores (alias: `scores`) +- `score_categories` (categoryOptions) - Categorical score values + +## Filter Examples +```json +[ + { + "type": "datetime", + "column": "timestamp", + "operator": ">=", + "value": "2024-01-01T00:00:00Z" + }, + { + "type": "string", + "column": "userId", + "operator": "=", + "value": "user-123" + }, + { + "type": "number", + "column": "totalCost", + "operator": ">=", + "value": 0.01 + }, + { + "type": "arrayOptions", + "column": "tags", + "operator": "all of", + "value": ["production", "critical"] + }, + { + "type": "stringObject", + "column": "metadata", + "key": "customer_tier", + "operator": "=", + "value": "enterprise" + } +] +``` + +## Performance Notes +- Filtering on `userId`, `sessionId`, or `metadata` may enable skip indexes for better query performance +- Score filters require a join with the scores table and may impact query performance diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index 49e7a40ee..b3a6cc31a 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -193,6 +193,8 @@ MembershipResponse, MembershipRole, MembershipsResponse, + OrganizationApiKey, + OrganizationApiKeysResponse, OrganizationProject, OrganizationProjectsResponse, ) @@ -399,6 +401,8 @@ "OpenAiResponseUsageSchema", "OpenAiUsage", "OptionalObservationBody", + "OrganizationApiKey", + "OrganizationApiKeysResponse", "OrganizationProject", "OrganizationProjectsResponse", "OtelAttribute", diff --git a/langfuse/api/resources/observations/client.py b/langfuse/api/resources/observations/client.py index 22c923399..83cc3274b 100644 --- a/langfuse/api/resources/observations/client.py +++ b/langfuse/api/resources/observations/client.py @@ -147,13 +147,15 @@ def get_many( Optional filter to only include observations with a certain version. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure Each filter condition has the following structure: ```json [ { "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on + "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" // - string: "=", "contains", "does not contain", "starts with", "ends with" @@ -171,6 +173,78 @@ def get_many( ] ``` + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Scores (requires join with scores table) + - `scores_avg` (number) - Average of numeric scores (alias: `scores`) + - `score_categories` (categoryOptions) - Categorical score values + + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -378,13 +452,15 @@ async def get_many( Optional filter to only include observations with a certain version. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure Each filter condition has the following structure: ```json [ { "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on + "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" // - string: "=", "contains", "does not contain", "starts with", "ends with" @@ -402,6 +478,78 @@ async def get_many( ] ``` + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Scores (requires join with scores table) + - `scores_avg` (number) - Average of numeric scores (alias: `scores`) + - `score_categories` (categoryOptions) - Categorical score values + + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/resources/organizations/__init__.py b/langfuse/api/resources/organizations/__init__.py index 5c5bfced3..36249ae36 100644 --- a/langfuse/api/resources/organizations/__init__.py +++ b/langfuse/api/resources/organizations/__init__.py @@ -7,6 +7,8 @@ MembershipResponse, MembershipRole, MembershipsResponse, + OrganizationApiKey, + OrganizationApiKeysResponse, OrganizationProject, OrganizationProjectsResponse, ) @@ -18,6 +20,8 @@ "MembershipResponse", "MembershipRole", "MembershipsResponse", + "OrganizationApiKey", + "OrganizationApiKeysResponse", "OrganizationProject", "OrganizationProjectsResponse", ] diff --git a/langfuse/api/resources/organizations/client.py b/langfuse/api/resources/organizations/client.py index b60f2d2bd..1e7bcd117 100644 --- a/langfuse/api/resources/organizations/client.py +++ b/langfuse/api/resources/organizations/client.py @@ -18,6 +18,7 @@ from .types.membership_request import MembershipRequest from .types.membership_response import MembershipResponse from .types.memberships_response import MembershipsResponse +from .types.organization_api_keys_response import OrganizationApiKeysResponse from .types.organization_projects_response import OrganizationProjectsResponse # this is used as the default value for optional parameters @@ -519,6 +520,68 @@ def get_organization_projects( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def get_organization_api_keys( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationApiKeysResponse: + """ + Get all API keys for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationApiKeysResponse + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.get_organization_api_keys() + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/apiKeys", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + OrganizationApiKeysResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + class AsyncOrganizationsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -1070,3 +1133,73 @@ async def main() -> None: except JSONDecodeError: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_organization_api_keys( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationApiKeysResponse: + """ + Get all API keys for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationApiKeysResponse + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.get_organization_api_keys() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/apiKeys", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + OrganizationApiKeysResponse, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/organizations/types/__init__.py b/langfuse/api/resources/organizations/types/__init__.py index d154f63d8..b3ea09797 100644 --- a/langfuse/api/resources/organizations/types/__init__.py +++ b/langfuse/api/resources/organizations/types/__init__.py @@ -6,6 +6,8 @@ from .membership_response import MembershipResponse from .membership_role import MembershipRole from .memberships_response import MembershipsResponse +from .organization_api_key import OrganizationApiKey +from .organization_api_keys_response import OrganizationApiKeysResponse from .organization_project import OrganizationProject from .organization_projects_response import OrganizationProjectsResponse @@ -16,6 +18,8 @@ "MembershipResponse", "MembershipRole", "MembershipsResponse", + "OrganizationApiKey", + "OrganizationApiKeysResponse", "OrganizationProject", "OrganizationProjectsResponse", ] diff --git a/langfuse/api/resources/organizations/types/organization_api_key.py b/langfuse/api/resources/organizations/types/organization_api_key.py new file mode 100644 index 000000000..ad54bb182 --- /dev/null +++ b/langfuse/api/resources/organizations/types/organization_api_key.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class OrganizationApiKey(pydantic_v1.BaseModel): + id: str + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") + expires_at: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="expiresAt", default=None + ) + last_used_at: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="lastUsedAt", default=None + ) + note: typing.Optional[str] = None + public_key: str = pydantic_v1.Field(alias="publicKey") + display_secret_key: str = pydantic_v1.Field(alias="displaySecretKey") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/organization_api_keys_response.py b/langfuse/api/resources/organizations/types/organization_api_keys_response.py new file mode 100644 index 000000000..e19ce6373 --- /dev/null +++ b/langfuse/api/resources/organizations/types/organization_api_keys_response.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .organization_api_key import OrganizationApiKey + + +class OrganizationApiKeysResponse(pydantic_v1.BaseModel): + api_keys: typing.List[OrganizationApiKey] = pydantic_v1.Field(alias="apiKeys") + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/trace/client.py b/langfuse/api/resources/trace/client.py index 7c5d803d7..e1f837f50 100644 --- a/langfuse/api/resources/trace/client.py +++ b/langfuse/api/resources/trace/client.py @@ -218,13 +218,15 @@ def list( Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + + ## Filter Structure Each filter condition has the following structure: ```json [ { "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on + "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" // - string: "=", "contains", "does not contain", "starts with", "ends with" @@ -242,6 +244,86 @@ def list( ] ``` + ## Available Columns + + ### Core Trace Fields + - `id` (string) - Trace ID + - `name` (string) - Trace name + - `timestamp` (datetime) - Trace timestamp + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + - `environment` (string) - Environment tag + - `version` (string) - Version tag + - `release` (string) - Release tag + - `tags` (arrayOptions) - Array of tags + - `bookmarked` (boolean) - Bookmark status + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Aggregated Metrics (from observations) + These metrics are aggregated from all observations within the trace: + - `latency` (number) - Latency in seconds (time from first observation start to last observation end) + - `inputTokens` (number) - Total input tokens across all observations + - `outputTokens` (number) - Total output tokens across all observations + - `totalTokens` (number) - Total tokens (alias: `tokens`) + - `inputCost` (number) - Total input cost in USD + - `outputCost` (number) - Total output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Observation Level Aggregations + These fields aggregate observation levels within the trace: + - `level` (string) - Highest severity level (ERROR > WARNING > DEFAULT > DEBUG) + - `warningCount` (number) - Count of WARNING level observations + - `errorCount` (number) - Count of ERROR level observations + - `defaultCount` (number) - Count of DEFAULT level observations + - `debugCount` (number) - Count of DEBUG level observations + + ### Scores (requires join with scores table) + - `scores_avg` (number) - Average of numeric scores (alias: `scores`) + - `score_categories` (categoryOptions) - Categorical score values + + ## Filter Examples + ```json + [ + { + "type": "datetime", + "column": "timestamp", + "operator": ">=", + "value": "2024-01-01T00:00:00Z" + }, + { + "type": "string", + "column": "userId", + "operator": "=", + "value": "user-123" + }, + { + "type": "number", + "column": "totalCost", + "operator": ">=", + "value": 0.01 + }, + { + "type": "arrayOptions", + "column": "tags", + "operator": "all of", + "value": ["production", "critical"] + }, + { + "type": "stringObject", + "column": "metadata", + "key": "customer_tier", + "operator": "=", + "value": "enterprise" + } + ] + ``` + + ## Performance Notes + - Filtering on `userId`, `sessionId`, or `metadata` may enable skip indexes for better query performance + - Score filters require a join with the scores table and may impact query performance + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -596,13 +678,15 @@ async def list( Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + + ## Filter Structure Each filter condition has the following structure: ```json [ { "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on + "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" // - string: "=", "contains", "does not contain", "starts with", "ends with" @@ -620,6 +704,86 @@ async def list( ] ``` + ## Available Columns + + ### Core Trace Fields + - `id` (string) - Trace ID + - `name` (string) - Trace name + - `timestamp` (datetime) - Trace timestamp + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + - `environment` (string) - Environment tag + - `version` (string) - Version tag + - `release` (string) - Release tag + - `tags` (arrayOptions) - Array of tags + - `bookmarked` (boolean) - Bookmark status + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Aggregated Metrics (from observations) + These metrics are aggregated from all observations within the trace: + - `latency` (number) - Latency in seconds (time from first observation start to last observation end) + - `inputTokens` (number) - Total input tokens across all observations + - `outputTokens` (number) - Total output tokens across all observations + - `totalTokens` (number) - Total tokens (alias: `tokens`) + - `inputCost` (number) - Total input cost in USD + - `outputCost` (number) - Total output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Observation Level Aggregations + These fields aggregate observation levels within the trace: + - `level` (string) - Highest severity level (ERROR > WARNING > DEFAULT > DEBUG) + - `warningCount` (number) - Count of WARNING level observations + - `errorCount` (number) - Count of ERROR level observations + - `defaultCount` (number) - Count of DEFAULT level observations + - `debugCount` (number) - Count of DEBUG level observations + + ### Scores (requires join with scores table) + - `scores_avg` (number) - Average of numeric scores (alias: `scores`) + - `score_categories` (categoryOptions) - Categorical score values + + ## Filter Examples + ```json + [ + { + "type": "datetime", + "column": "timestamp", + "operator": ">=", + "value": "2024-01-01T00:00:00Z" + }, + { + "type": "string", + "column": "userId", + "operator": "=", + "value": "user-123" + }, + { + "type": "number", + "column": "totalCost", + "operator": ">=", + "value": 0.01 + }, + { + "type": "arrayOptions", + "column": "tags", + "operator": "all of", + "value": ["production", "critical"] + }, + { + "type": "stringObject", + "column": "metadata", + "key": "customer_tier", + "operator": "=", + "value": "enterprise" + } + ] + ``` + + ## Performance Notes + - Filtering on `userId`, `sessionId`, or `metadata` may enable skip indexes for better query performance + - Score filters require a join with the scores table and may impact query performance + request_options : typing.Optional[RequestOptions] Request-specific configuration. From d873cfb31c27698725956888e2f2b4ea0f0223b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:56:37 +0100 Subject: [PATCH 093/296] chore(deps-dev): bump langgraph-checkpoint from 2.1.2 to 3.0.0 (#1427) Bumps [langgraph-checkpoint](https://github.com/langchain-ai/langgraph) from 2.1.2 to 3.0.0. - [Release notes](https://github.com/langchain-ai/langgraph/releases) - [Commits](https://github.com/langchain-ai/langgraph/compare/checkpoint==2.1.2...checkpoint==3.0.0) --- updated-dependencies: - dependency-name: langgraph-checkpoint dependency-version: 3.0.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 144 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 25 deletions(-) diff --git a/poetry.lock b/poetry.lock index 378d67caf..a8d49f84f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -37,6 +39,7 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -48,6 +51,7 @@ version = "0.0.130" description = "Universal library for evaluating AI models" optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, @@ -71,6 +75,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -82,6 +87,8 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -93,6 +100,7 @@ version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, @@ -104,6 +112,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -115,6 +124,7 @@ version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, @@ -237,6 +247,7 @@ version = "0.14.0" description = "Mustache templating language renderer" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, @@ -248,10 +259,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -259,6 +272,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -270,6 +284,7 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -281,6 +296,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -298,6 +315,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -312,6 +330,7 @@ version = "3.20.0" description = "A platform independent file lock." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, @@ -323,6 +342,7 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -340,6 +360,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -351,6 +372,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -372,6 +394,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -384,7 +407,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -396,6 +419,7 @@ version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, @@ -410,6 +434,7 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -424,6 +449,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -433,12 +459,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -447,6 +473,7 @@ version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -458,6 +485,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -475,6 +503,7 @@ version = "0.11.1" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, @@ -586,6 +615,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -600,6 +630,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -611,6 +642,7 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -632,6 +664,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -646,6 +679,7 @@ version = "1.0.1" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" +groups = ["dev"] files = [ {file = "langchain-1.0.1-py3-none-any.whl", hash = "sha256:48fd616413fee4843f12cad49e8b74ad6fc159640142ca885d03bd925cd24503"}, {file = "langchain-1.0.1.tar.gz", hash = "sha256:8bf60e096ca9c06626d9161a46651405ef1d12b5863f7679283666f83f760dc5"}, @@ -679,6 +713,7 @@ version = "1.0.0" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" +groups = ["dev"] files = [ {file = "langchain_core-1.0.0-py3-none-any.whl", hash = "sha256:a94561bf75dd097c7d6e3864950f28dadc963f0bd810114de4095f41f634059b"}, {file = "langchain_core-1.0.0.tar.gz", hash = "sha256:8e81c94a22fa3a362a0f101bbd1271bf3725e50cf1e31c84e8f4a1c731279785"}, @@ -699,6 +734,7 @@ version = "0.3.34" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0.0,>=3.9.0" +groups = ["dev"] files = [ {file = "langchain_openai-0.3.34-py3-none-any.whl", hash = "sha256:08d61d68a6d869c70d542171e149b9065668dedfc4fafcd4de8aeb5b933030a9"}, {file = "langchain_openai-0.3.34.tar.gz", hash = "sha256:57916d462be5b8fd19e5cb2f00d4e5cf0465266a292d583de2fc693a55ba190e"}, @@ -711,32 +747,34 @@ tiktoken = ">=0.7.0,<1.0.0" [[package]] name = "langgraph" -version = "1.0.0" +version = "1.0.2" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph-1.0.0-py3-none-any.whl", hash = "sha256:4d478781832a1bc67e06c3eb571412ec47d7c57a5467d1f3775adf0e9dd4042c"}, - {file = "langgraph-1.0.0.tar.gz", hash = "sha256:5f83ed0e9bbcc37635bc49cbc9b3d9306605fa07504f955b7a871ed715f9964c"}, + {file = "langgraph-1.0.2-py3-none-any.whl", hash = "sha256:b3d56b8c01de857b5fb1da107e8eab6e30512a377685eeedb4f76456724c9729"}, + {file = "langgraph-1.0.2.tar.gz", hash = "sha256:dae1af08d6025cb1fcaed68f502c01af7d634d9044787c853a46c791cfc52f67"}, ] [package.dependencies] langchain-core = ">=0.1" -langgraph-checkpoint = ">=2.1.0,<3.0.0" -langgraph-prebuilt = ">=1.0.0,<1.1.0" +langgraph-checkpoint = ">=2.1.0,<4.0.0" +langgraph-prebuilt = ">=1.0.2,<1.1.0" langgraph-sdk = ">=0.2.2,<0.3.0" pydantic = ">=2.7.4" xxhash = ">=3.5.0" [[package]] name = "langgraph-checkpoint" -version = "2.1.2" +version = "3.0.0" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60"}, - {file = "langgraph_checkpoint-2.1.2.tar.gz", hash = "sha256:112e9d067a6eff8937caf198421b1ffba8d9207193f14ac6f89930c1260c06f9"}, + {file = "langgraph_checkpoint-3.0.0-py3-none-any.whl", hash = "sha256:560beb83e629784ab689212a3d60834fb3196b4bbe1d6ac18e5cad5d85d46010"}, + {file = "langgraph_checkpoint-3.0.0.tar.gz", hash = "sha256:f738695ad938878d8f4775d907d9629e9fcd345b1950196effb08f088c52369e"}, ] [package.dependencies] @@ -745,18 +783,19 @@ ormsgpack = ">=1.10.0" [[package]] name = "langgraph-prebuilt" -version = "1.0.0" +version = "1.0.2" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph_prebuilt-1.0.0-py3-none-any.whl", hash = "sha256:ceaae4c5cee8c1f9b6468f76c114cafebb748aed0c93483b7c450e5a89de9c61"}, - {file = "langgraph_prebuilt-1.0.0.tar.gz", hash = "sha256:eb75dad9aca0137451ca0395aa8541a665b3f60979480b0431d626fd195dcda2"}, + {file = "langgraph_prebuilt-1.0.2-py3-none-any.whl", hash = "sha256:d9499f7c449fb637ee7b87e3f6a3b74095f4202053c74d33894bd839ea4c57c7"}, + {file = "langgraph_prebuilt-1.0.2.tar.gz", hash = "sha256:9896dbabf04f086eb59df4294f54ab5bdb21cd78e27e0a10e695dffd1cc6097d"}, ] [package.dependencies] -langchain-core = ">=0.3.67" -langgraph-checkpoint = ">=2.1.0,<3.0.0" +langchain-core = ">=1.0.0" +langgraph-checkpoint = ">=2.1.0,<4.0.0" [[package]] name = "langgraph-sdk" @@ -764,6 +803,7 @@ version = "0.2.9" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langgraph_sdk-0.2.9-py3-none-any.whl", hash = "sha256:fbf302edadbf0fb343596f91c597794e936ef68eebc0d3e1d358b6f9f72a1429"}, {file = "langgraph_sdk-0.2.9.tar.gz", hash = "sha256:b3bd04c6be4fa382996cd2be8fbc1e7cc94857d2bc6b6f4599a7f2a245975303"}, @@ -779,6 +819,7 @@ version = "0.4.37" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "langsmith-0.4.37-py3-none-any.whl", hash = "sha256:e34a94ce7277646299e4703a0f6e2d2c43647a28e8b800bb7ef82fd87a0ec766"}, {file = "langsmith-0.4.37.tar.gz", hash = "sha256:d9a0eb6dd93f89843ac982c9f92be93cf2bcabbe19957f362c547766c7366c71"}, @@ -794,7 +835,7 @@ requests-toolbelt = ">=1.0.0" zstandard = ">=0.23.0" [package.extras] -claude-agent-sdk = ["claude-agent-sdk (>=0.1.0)"] +claude-agent-sdk = ["claude-agent-sdk (>=0.1.0) ; python_version >= \"3.10\""] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] openai-agents = ["openai-agents (>=0.0.3)"] otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] @@ -807,6 +848,7 @@ version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -905,6 +947,7 @@ version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, @@ -965,6 +1008,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -976,6 +1020,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -987,6 +1032,7 @@ version = "2.5.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "openai-2.5.0-py3-none-any.whl", hash = "sha256:21380e5f52a71666dbadbf322dd518bdf2b9d11ed0bb3f96bea17310302d6280"}, {file = "openai-2.5.0.tar.gz", hash = "sha256:f8fa7611f96886a0f31ac6b97e58bc0ada494b255ee2cfd51c8eb502cfcb4814"}, @@ -1014,6 +1060,7 @@ version = "1.38.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582"}, {file = "opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12"}, @@ -1029,6 +1076,7 @@ version = "1.38.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a"}, {file = "opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c"}, @@ -1043,6 +1091,7 @@ version = "1.38.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b"}, {file = "opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b"}, @@ -1063,6 +1112,7 @@ version = "0.59b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee"}, {file = "opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc"}, @@ -1080,6 +1130,7 @@ version = "0.59b0" description = "Thread context propagation support for OpenTelemetry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca"}, {file = "opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3"}, @@ -1096,6 +1147,7 @@ version = "1.38.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18"}, {file = "opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468"}, @@ -1110,6 +1162,7 @@ version = "1.38.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b"}, {file = "opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe"}, @@ -1126,6 +1179,7 @@ version = "0.59b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed"}, {file = "opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0"}, @@ -1141,6 +1195,7 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1233,6 +1288,7 @@ version = "1.11.0" description = "" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "ormsgpack-1.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:03d4e658dd6e1882a552ce1d13cc7b49157414e7d56a4091fbe7823225b08cba"}, {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb67eb913c2b703f0ed39607fc56e50724dd41f92ce080a586b4d6149eb3fe4"}, @@ -1298,6 +1354,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1309,6 +1366,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1320,6 +1378,7 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1336,6 +1395,7 @@ version = "4.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, @@ -1352,6 +1412,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1367,6 +1428,7 @@ version = "0.9.0" description = "A fast C-implemented library for Levenshtein distance" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, @@ -1431,6 +1493,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1449,6 +1512,7 @@ version = "6.33.0" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035"}, {file = "protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee"}, @@ -1468,6 +1532,7 @@ version = "2.12.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, @@ -1481,7 +1546,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1489,6 +1554,7 @@ version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, @@ -1618,6 +1684,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1632,6 +1699,7 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1655,6 +1723,7 @@ version = "1.1.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_asyncio-1.1.1-py3-none-any.whl", hash = "sha256:726339d30fcfde24691f589445b9b67d058b311ac632b1d704e97f20f1d878da"}, {file = "pytest_asyncio-1.1.1.tar.gz", hash = "sha256:b72d215c38e2c91dbb32f275e0b5be69602d7869910e109360e375129960a649"}, @@ -1674,6 +1743,7 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1688,6 +1758,7 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1702,6 +1773,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1722,6 +1794,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -1804,6 +1877,7 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -1820,6 +1894,7 @@ version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, @@ -1944,6 +2019,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1965,6 +2041,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -1979,6 +2056,7 @@ version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, @@ -2143,6 +2221,7 @@ version = "0.12.12" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc"}, {file = "ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727"}, @@ -2171,6 +2250,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2182,6 +2262,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -2197,6 +2278,7 @@ version = "0.12.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, @@ -2270,6 +2352,8 @@ version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -2321,6 +2405,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2342,6 +2427,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2353,6 +2439,7 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -2367,13 +2454,14 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2384,6 +2472,7 @@ version = "20.35.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a"}, {file = "virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44"}, @@ -2397,7 +2486,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "werkzeug" @@ -2405,6 +2494,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -2422,6 +2512,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2512,6 +2603,7 @@ version = "3.6.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, @@ -2661,13 +2753,14 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2680,6 +2773,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -2783,9 +2877,9 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0)", "cffi (>=2.0.0b)"] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.10,<4.0" content-hash = "bb4ec20d58e29f5d71599357de616571eeae016818d5c246774f0c5bc01d3d0e" From 52d17d32b7993e7947823af0be8e9347e941895c Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 6 Nov 2025 19:12:28 +0100 Subject: [PATCH 094/296] feat(api): update API spec from langfuse/langfuse 25e6ad9 (#1428) Co-authored-by: langfuse-bot --- langfuse/api/resources/commons/types/dataset.py | 14 ++++++++++++++ .../datasets/types/create_dataset_request.py | 15 +++++++++++++++ .../resources/score/types/create_score_request.py | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/langfuse/api/resources/commons/types/dataset.py b/langfuse/api/resources/commons/types/dataset.py index be59a951a..116bff135 100644 --- a/langfuse/api/resources/commons/types/dataset.py +++ b/langfuse/api/resources/commons/types/dataset.py @@ -12,6 +12,20 @@ class Dataset(pydantic_v1.BaseModel): name: str description: typing.Optional[str] = None metadata: typing.Optional[typing.Any] = None + input_schema: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="inputSchema", default=None + ) + """ + JSON Schema for validating dataset item inputs + """ + + expected_output_schema: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="expectedOutputSchema", default=None + ) + """ + JSON Schema for validating dataset item expected outputs + """ + project_id: str = pydantic_v1.Field(alias="projectId") created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") diff --git a/langfuse/api/resources/datasets/types/create_dataset_request.py b/langfuse/api/resources/datasets/types/create_dataset_request.py index 023cb4c12..228527909 100644 --- a/langfuse/api/resources/datasets/types/create_dataset_request.py +++ b/langfuse/api/resources/datasets/types/create_dataset_request.py @@ -11,6 +11,19 @@ class CreateDatasetRequest(pydantic_v1.BaseModel): name: str description: typing.Optional[str] = None metadata: typing.Optional[typing.Any] = None + input_schema: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="inputSchema", default=None + ) + """ + JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. + """ + + expected_output_schema: typing.Optional[typing.Any] = pydantic_v1.Field( + alias="expectedOutputSchema", default=None + ) + """ + JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. + """ def json(self, **kwargs: typing.Any) -> str: kwargs_with_defaults: typing.Any = { @@ -40,5 +53,7 @@ def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: class Config: frozen = True smart_union = True + allow_population_by_field_name = True + populate_by_name = True extra = pydantic_v1.Extra.allow json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score/types/create_score_request.py b/langfuse/api/resources/score/types/create_score_request.py index 36b0774ee..1f79f4a64 100644 --- a/langfuse/api/resources/score/types/create_score_request.py +++ b/langfuse/api/resources/score/types/create_score_request.py @@ -40,7 +40,7 @@ class CreateScoreRequest(pydantic_v1.BaseModel): """ comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None environment: typing.Optional[str] = pydantic_v1.Field(default=None) """ The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. From 0b1f2c00929e9c7815ceac3828d6f22e752f9cf3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:24:02 +0100 Subject: [PATCH 095/296] chore: release v3.9.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 96bad3013..90aec85e2 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.9.0" +__version__ = "3.9.1" diff --git a/pyproject.toml b/pyproject.toml index a118c3127..3a9ff921d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.9.0" +version = "3.9.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From d8c624a882884428604e1464081fe2996a75ba46 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:37:40 +0100 Subject: [PATCH 096/296] fix(client): handle update_current_trace on non recorded spans gracefully (#1432) --- langfuse/_client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 55a9667e3..ae5eba235 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -1664,7 +1664,7 @@ def update_current_trace( current_otel_span = self._get_current_otel_span() - if current_otel_span is not None: + if current_otel_span is not None and current_otel_span.is_recording(): existing_observation_type = current_otel_span.attributes.get( # type: ignore[attr-defined] LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "span" ) From 9228e7d3b3882986d652b3624c992e8d7625eed5 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:54:47 +0100 Subject: [PATCH 097/296] fix(experiments): remove evals from main execution span (#1433) --- langfuse/_client/client.py | 88 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ae5eba235..d2faaba83 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2858,56 +2858,56 @@ async def _process_experiment_item( metadata=final_observation_metadata, ) - # Run evaluators - evaluations = [] + except Exception as e: + span.update( + output=f"Error: {str(e)}", level="ERROR", status_message=str(e) + ) + raise e - for evaluator in evaluators: - try: - eval_metadata: Optional[Dict[str, Any]] = None + # Run evaluators + evaluations = [] - if isinstance(item, dict): - eval_metadata = item.get("metadata") - elif hasattr(item, "metadata"): - eval_metadata = item.metadata + for evaluator in evaluators: + try: + eval_metadata: Optional[Dict[str, Any]] = None - eval_results = await _run_evaluator( - evaluator, - input=input_data, - output=output, - expected_output=expected_output, - metadata=eval_metadata, - ) - evaluations.extend(eval_results) - - # Store evaluations as scores - for evaluation in eval_results: - self.create_score( - trace_id=trace_id, - observation_id=span.id, - name=evaluation.name, - value=evaluation.value, # type: ignore - comment=evaluation.comment, - metadata=evaluation.metadata, - config_id=evaluation.config_id, - data_type=evaluation.data_type, # type: ignore - ) + if isinstance(item, dict): + eval_metadata = item.get("metadata") + elif hasattr(item, "metadata"): + eval_metadata = item.metadata - except Exception as e: - langfuse_logger.error(f"Evaluator failed: {e}") + eval_results = await _run_evaluator( + evaluator, + input=input_data, + output=output, + expected_output=expected_output, + metadata=eval_metadata, + ) + evaluations.extend(eval_results) + + # Store evaluations as scores + for evaluation in eval_results: + self.create_score( + trace_id=trace_id, + observation_id=span.id, + name=evaluation.name, + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=evaluation.metadata, + config_id=evaluation.config_id, + data_type=evaluation.data_type, # type: ignore + ) - return ExperimentItemResult( - item=item, - output=output, - evaluations=evaluations, - trace_id=trace_id, - dataset_run_id=dataset_run_id, - ) + except Exception as e: + langfuse_logger.error(f"Evaluator failed: {e}") - except Exception as e: - span.update( - output=f"Error: {str(e)}", level="ERROR", status_message=str(e) - ) - raise e + return ExperimentItemResult( + item=item, + output=output, + evaluations=evaluations, + trace_id=trace_id, + dataset_run_id=dataset_run_id, + ) def _create_experiment_run_name( self, *, name: Optional[str] = None, run_name: Optional[str] = None From aa7819f210e2e8e53a1497f831013f6850a7b2a5 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:19:52 +0100 Subject: [PATCH 098/296] chore: release v3.9.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 90aec85e2..43bdef350 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.9.1" +__version__ = "3.9.2" diff --git a/pyproject.toml b/pyproject.toml index 3a9ff921d..be0f4dded 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.9.1" +version = "3.9.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 9ec9f4acc16f3c572669db3cbf5e2babf2b43cd7 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 12 Nov 2025 13:36:52 +0100 Subject: [PATCH 099/296] feat(api): update API spec from langfuse/langfuse db01ff9 (#1435) Co-authored-by: langfuse-bot --- langfuse/api/resources/commons/types/categorical_score.py | 4 ++-- langfuse/api/resources/commons/types/categorical_score_v_1.py | 4 ++-- langfuse/api/resources/commons/types/score.py | 2 +- langfuse/api/resources/commons/types/score_v_1.py | 2 +- .../api/resources/score_v_2/types/get_scores_response_data.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/langfuse/api/resources/commons/types/categorical_score.py b/langfuse/api/resources/commons/types/categorical_score.py index 847caf47b..363ed03ff 100644 --- a/langfuse/api/resources/commons/types/categorical_score.py +++ b/langfuse/api/resources/commons/types/categorical_score.py @@ -9,9 +9,9 @@ class CategoricalScore(BaseScore): - value: typing.Optional[float] = pydantic_v1.Field(default=None) + value: float = pydantic_v1.Field() """ - Only defined if a config is linked. Represents the numeric category mapping of the stringValue + Represents the numeric category mapping of the stringValue. If no config is linked, defaults to 0. """ string_value: str = pydantic_v1.Field(alias="stringValue") diff --git a/langfuse/api/resources/commons/types/categorical_score_v_1.py b/langfuse/api/resources/commons/types/categorical_score_v_1.py index 2cc84fc25..2aa42d586 100644 --- a/langfuse/api/resources/commons/types/categorical_score_v_1.py +++ b/langfuse/api/resources/commons/types/categorical_score_v_1.py @@ -9,9 +9,9 @@ class CategoricalScoreV1(BaseScoreV1): - value: typing.Optional[float] = pydantic_v1.Field(default=None) + value: float = pydantic_v1.Field() """ - Only defined if a config is linked. Represents the numeric category mapping of the stringValue + Represents the numeric category mapping of the stringValue. If no config is linked, defaults to 0. """ string_value: str = pydantic_v1.Field(alias="stringValue") diff --git a/langfuse/api/resources/commons/types/score.py b/langfuse/api/resources/commons/types/score.py index 789854afc..f0b866067 100644 --- a/langfuse/api/resources/commons/types/score.py +++ b/langfuse/api/resources/commons/types/score.py @@ -75,7 +75,7 @@ class Config: class Score_Categorical(pydantic_v1.BaseModel): - value: typing.Optional[float] = None + value: float string_value: str = pydantic_v1.Field(alias="stringValue") id: str trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) diff --git a/langfuse/api/resources/commons/types/score_v_1.py b/langfuse/api/resources/commons/types/score_v_1.py index 788f6f39c..191e0d96f 100644 --- a/langfuse/api/resources/commons/types/score_v_1.py +++ b/langfuse/api/resources/commons/types/score_v_1.py @@ -69,7 +69,7 @@ class Config: class ScoreV1_Categorical(pydantic_v1.BaseModel): - value: typing.Optional[float] = None + value: float string_value: str = pydantic_v1.Field(alias="stringValue") id: str trace_id: str = pydantic_v1.Field(alias="traceId") diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py index 63f41f4c4..e09f31cb9 100644 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py +++ b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py @@ -78,7 +78,7 @@ class Config: class GetScoresResponseData_Categorical(pydantic_v1.BaseModel): trace: typing.Optional[GetScoresResponseTraceData] = None - value: typing.Optional[float] = None + value: float string_value: str = pydantic_v1.Field(alias="stringValue") id: str trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) From fd7e850ec1915e2faf31b9e940484c673c0497d0 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 12 Nov 2025 15:49:06 +0100 Subject: [PATCH 100/296] fix(experiments): move evals out of root span (#1437) --- langfuse/_client/client.py | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index d2faaba83..2b3d6671c 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2864,50 +2864,50 @@ async def _process_experiment_item( ) raise e - # Run evaluators - evaluations = [] + # Run evaluators + evaluations = [] - for evaluator in evaluators: - try: - eval_metadata: Optional[Dict[str, Any]] = None + for evaluator in evaluators: + try: + eval_metadata: Optional[Dict[str, Any]] = None - if isinstance(item, dict): - eval_metadata = item.get("metadata") - elif hasattr(item, "metadata"): - eval_metadata = item.metadata + if isinstance(item, dict): + eval_metadata = item.get("metadata") + elif hasattr(item, "metadata"): + eval_metadata = item.metadata - eval_results = await _run_evaluator( - evaluator, - input=input_data, - output=output, - expected_output=expected_output, - metadata=eval_metadata, + eval_results = await _run_evaluator( + evaluator, + input=input_data, + output=output, + expected_output=expected_output, + metadata=eval_metadata, + ) + evaluations.extend(eval_results) + + # Store evaluations as scores + for evaluation in eval_results: + self.create_score( + trace_id=trace_id, + observation_id=span.id, + name=evaluation.name, + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=evaluation.metadata, + config_id=evaluation.config_id, + data_type=evaluation.data_type, # type: ignore ) - evaluations.extend(eval_results) - - # Store evaluations as scores - for evaluation in eval_results: - self.create_score( - trace_id=trace_id, - observation_id=span.id, - name=evaluation.name, - value=evaluation.value, # type: ignore - comment=evaluation.comment, - metadata=evaluation.metadata, - config_id=evaluation.config_id, - data_type=evaluation.data_type, # type: ignore - ) - except Exception as e: - langfuse_logger.error(f"Evaluator failed: {e}") + except Exception as e: + langfuse_logger.error(f"Evaluator failed: {e}") - return ExperimentItemResult( - item=item, - output=output, - evaluations=evaluations, - trace_id=trace_id, - dataset_run_id=dataset_run_id, - ) + return ExperimentItemResult( + item=item, + output=output, + evaluations=evaluations, + trace_id=trace_id, + dataset_run_id=dataset_run_id, + ) def _create_experiment_run_name( self, *, name: Optional[str] = None, run_name: Optional[str] = None From 94b0211848fc17ca2d292abe1a541b5b35caa467 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 12 Nov 2025 15:51:16 +0100 Subject: [PATCH 101/296] chore: release v3.9.3 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 43bdef350..58ea94dd5 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.9.2" +__version__ = "3.9.3" diff --git a/pyproject.toml b/pyproject.toml index be0f4dded..fc5a0225a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.9.2" +version = "3.9.3" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 4c5700764d2816de06847ed04a03d82c2d830f9f Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:14:30 +0100 Subject: [PATCH 102/296] feat(evals): add run_batched_evaluation (#1436) --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 3 +- langfuse/__init__.py | 14 + langfuse/_client/client.py | 297 +++++- langfuse/_client/datasets.py | 7 + langfuse/_client/observe.py | 8 +- langfuse/batch_evaluation.py | 1558 ++++++++++++++++++++++++++++ tests/test_batch_evaluation.py | 1077 +++++++++++++++++++ tests/test_core_sdk.py | 66 +- tests/test_decorators.py | 42 +- tests/test_deprecation.py | 12 +- tests/test_experiments.py | 152 ++- tests/test_langchain.py | 6 +- tests/test_otel.py | 210 ++-- tests/test_prompt_atexit.py | 12 +- tests/test_propagate_attributes.py | 6 +- 16 files changed, 3242 insertions(+), 230 deletions(-) create mode 100644 langfuse/batch_evaluation.py create mode 100644 tests/test_batch_evaluation.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cb4cb3d6..344225dcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: - name: Setup node (for langfuse server) uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Cache langfuse server dependencies uses: actions/cache@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 59073b603..b62426d7b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.2 + rev: v0.14.4 hooks: # Run the linter and fix - id: ruff @@ -10,6 +10,7 @@ repos: # Run the formatter. - id: ruff-format types_or: [python, pyi, jupyter] + args: [--config=ci.ruff.toml] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.18.2 diff --git a/langfuse/__init__.py b/langfuse/__init__.py index f96b18bc8..ab4ceedb1 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -1,5 +1,13 @@ """.. include:: ../README.md""" +from langfuse.batch_evaluation import ( + BatchEvaluationResult, + BatchEvaluationResumeToken, + CompositeEvaluatorFunction, + EvaluatorInputs, + EvaluatorStats, + MapperFunction, +) from langfuse.experiment import Evaluation from ._client import client as _client_module @@ -41,6 +49,12 @@ "LangfuseRetriever", "LangfuseGuardrail", "Evaluation", + "EvaluatorInputs", + "MapperFunction", + "CompositeEvaluatorFunction", + "EvaluatorStats", + "BatchEvaluationResumeToken", + "BatchEvaluationResult", "experiment", "api", ] diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 2b3d6671c..3a432da25 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -86,6 +86,13 @@ Prompt_Chat, Prompt_Text, ) +from langfuse.batch_evaluation import ( + BatchEvaluationResult, + BatchEvaluationResumeToken, + BatchEvaluationRunner, + CompositeEvaluatorFunction, + MapperFunction, +) from langfuse.experiment import ( Evaluation, EvaluatorFunction, @@ -2458,6 +2465,7 @@ def run_experiment( data: ExperimentData, task: TaskFunction, evaluators: List[EvaluatorFunction] = [], + composite_evaluator: Optional[CompositeEvaluatorFunction] = None, run_evaluators: List[RunEvaluatorFunction] = [], max_concurrency: int = 50, metadata: Optional[Dict[str, str]] = None, @@ -2493,6 +2501,10 @@ def run_experiment( evaluators: List of functions to evaluate each item's output individually. Each evaluator receives input, output, expected_output, and metadata. Can return single Evaluation dict or list of Evaluation dicts. + composite_evaluator: Optional function that creates composite scores from item-level evaluations. + Receives the same inputs as item-level evaluators (input, output, expected_output, metadata) + plus the list of evaluations from item-level evaluators. Useful for weighted averages, + pass/fail decisions based on multiple criteria, or custom scoring logic combining multiple metrics. run_evaluators: List of functions to evaluate the entire experiment run. Each run evaluator receives all item_results and can compute aggregate metrics. Useful for calculating averages, distributions, or cross-item comparisons. @@ -2630,6 +2642,7 @@ def average_accuracy(*, item_results, **kwargs): data=data, task=task, evaluators=evaluators or [], + composite_evaluator=composite_evaluator, run_evaluators=run_evaluators or [], max_concurrency=max_concurrency, metadata=metadata, @@ -2646,6 +2659,7 @@ async def _run_experiment_async( data: ExperimentData, task: TaskFunction, evaluators: List[EvaluatorFunction], + composite_evaluator: Optional[CompositeEvaluatorFunction], run_evaluators: List[RunEvaluatorFunction], max_concurrency: int, metadata: Optional[Dict[str, Any]] = None, @@ -2661,7 +2675,14 @@ async def _run_experiment_async( async def process_item(item: ExperimentItem) -> ExperimentItemResult: async with semaphore: return await self._process_experiment_item( - item, task, evaluators, name, run_name, description, metadata + item, + task, + evaluators, + composite_evaluator, + name, + run_name, + description, + metadata, ) # Run all items concurrently @@ -2743,6 +2764,7 @@ async def _process_experiment_item( item: ExperimentItem, task: Callable, evaluators: List[Callable], + composite_evaluator: Optional[CompositeEvaluatorFunction], experiment_name: str, experiment_run_name: str, experiment_description: Optional[str], @@ -2901,6 +2923,51 @@ async def _process_experiment_item( except Exception as e: langfuse_logger.error(f"Evaluator failed: {e}") + # Run composite evaluator if provided and we have evaluations + if composite_evaluator and evaluations: + try: + composite_eval_metadata: Optional[Dict[str, Any]] = None + if isinstance(item, dict): + composite_eval_metadata = item.get("metadata") + elif hasattr(item, "metadata"): + composite_eval_metadata = item.metadata + + result = composite_evaluator( + input=input_data, + output=output, + expected_output=expected_output, + metadata=composite_eval_metadata, + evaluations=evaluations, + ) + + # Handle async composite evaluators + if asyncio.iscoroutine(result): + result = await result + + # Normalize to list + composite_evals: List[Evaluation] = [] + if isinstance(result, (dict, Evaluation)): + composite_evals = [result] # type: ignore + elif isinstance(result, list): + composite_evals = result # type: ignore + + # Store composite evaluations as scores and add to evaluations list + for composite_evaluation in composite_evals: + self.create_score( + trace_id=trace_id, + observation_id=span.id, + name=composite_evaluation.name, + value=composite_evaluation.value, # type: ignore + comment=composite_evaluation.comment, + metadata=composite_evaluation.metadata, + config_id=composite_evaluation.config_id, + data_type=composite_evaluation.data_type, # type: ignore + ) + evaluations.append(composite_evaluation) + + except Exception as e: + langfuse_logger.error(f"Composite evaluator failed: {e}") + return ExperimentItemResult( item=item, output=output, @@ -2919,6 +2986,234 @@ def _create_experiment_run_name( return f"{name} - {iso_timestamp}" + def run_batched_evaluation( + self, + *, + scope: Literal["traces", "observations"], + mapper: MapperFunction, + filter: Optional[str] = None, + fetch_batch_size: int = 50, + max_items: Optional[int] = None, + max_retries: int = 3, + evaluators: List[EvaluatorFunction], + composite_evaluator: Optional[CompositeEvaluatorFunction] = None, + max_concurrency: int = 50, + metadata: Optional[Dict[str, Any]] = None, + resume_from: Optional[BatchEvaluationResumeToken] = None, + verbose: bool = False, + ) -> BatchEvaluationResult: + """Fetch traces or observations and run evaluations on each item. + + This method provides a powerful way to evaluate existing data in Langfuse at scale. + It fetches items based on filters, transforms them using a mapper function, runs + evaluators on each item, and creates scores that are linked back to the original + entities. This is ideal for: + + - Running evaluations on production traces after deployment + - Backtesting new evaluation metrics on historical data + - Batch scoring of observations for quality monitoring + - Periodic evaluation runs on recent data + + The method uses a streaming/pipeline approach to process items in batches, making + it memory-efficient for large datasets. It includes comprehensive error handling, + retry logic, and resume capability for long-running evaluations. + + Args: + scope: The type of items to evaluate. Must be one of: + - "traces": Evaluate complete traces with all their observations + - "observations": Evaluate individual observations (spans, generations, events) + mapper: Function that transforms API response objects into evaluator inputs. + Receives a trace/observation object and returns an EvaluatorInputs + instance with input, output, expected_output, and metadata fields. + Can be sync or async. + evaluators: List of evaluation functions to run on each item. Each evaluator + receives the mapped inputs and returns Evaluation object(s). Evaluator + failures are logged but don't stop the batch evaluation. + filter: Optional JSON filter string for querying items (same format as Langfuse API). Examples: + - '{"tags": ["production"]}' + - '{"user_id": "user123", "timestamp": {"operator": ">", "value": "2024-01-01"}}' + Default: None (fetches all items). + fetch_batch_size: Number of items to fetch per API call and hold in memory. + Larger values may be faster but use more memory. Default: 50. + max_items: Maximum total number of items to process. If None, processes all + items matching the filter. Useful for testing or limiting evaluation runs. + Default: None (process all). + max_concurrency: Maximum number of items to evaluate concurrently. Controls + parallelism and resource usage. Default: 50. + composite_evaluator: Optional function that creates a composite score from + item-level evaluations. Receives the original item and its evaluations, + returns a single Evaluation. Useful for weighted averages or combined metrics. + Default: None. + metadata: Optional metadata dict to add to all created scores. Useful for + tracking evaluation runs, versions, or other context. Default: None. + max_retries: Maximum number of retry attempts for failed batch fetches. + Uses exponential backoff (1s, 2s, 4s). Default: 3. + verbose: If True, logs progress information to console. Useful for monitoring + long-running evaluations. Default: False. + resume_from: Optional resume token from a previous incomplete run. Allows + continuing evaluation after interruption or failure. Default: None. + + + Returns: + BatchEvaluationResult containing: + - total_items_fetched: Number of items fetched from API + - total_items_processed: Number of items successfully evaluated + - total_items_failed: Number of items that failed evaluation + - total_scores_created: Scores created by item-level evaluators + - total_composite_scores_created: Scores created by composite evaluator + - total_evaluations_failed: Individual evaluator failures + - evaluator_stats: Per-evaluator statistics (success rate, scores created) + - resume_token: Token for resuming if incomplete (None if completed) + - completed: True if all items processed + - duration_seconds: Total execution time + - failed_item_ids: IDs of items that failed + - error_summary: Error types and counts + - has_more_items: True if max_items reached but more exist + + Raises: + ValueError: If invalid scope is provided. + + Examples: + Basic trace evaluation: + ```python + from langfuse import Langfuse, EvaluatorInputs, Evaluation + + client = Langfuse() + + # Define mapper to extract fields from traces + def trace_mapper(trace): + return EvaluatorInputs( + input=trace.input, + output=trace.output, + expected_output=None, + metadata={"trace_id": trace.id} + ) + + # Define evaluator + def length_evaluator(*, input, output, expected_output, metadata): + return Evaluation( + name="output_length", + value=len(output) if output else 0 + ) + + # Run batch evaluation + result = client.run_batched_evaluation( + scope="traces", + mapper=trace_mapper, + evaluators=[length_evaluator], + filter='{"tags": ["production"]}', + max_items=1000, + verbose=True + ) + + print(f"Processed {result.total_items_processed} traces") + print(f"Created {result.total_scores_created} scores") + ``` + + Evaluation with composite scorer: + ```python + def accuracy_evaluator(*, input, output, expected_output, metadata): + # ... evaluation logic + return Evaluation(name="accuracy", value=0.85) + + def relevance_evaluator(*, input, output, expected_output, metadata): + # ... evaluation logic + return Evaluation(name="relevance", value=0.92) + + def composite_evaluator(*, item, evaluations): + # Weighted average of evaluations + weights = {"accuracy": 0.6, "relevance": 0.4} + total = sum( + e.value * weights.get(e.name, 0) + for e in evaluations + if isinstance(e.value, (int, float)) + ) + return Evaluation( + name="composite_score", + value=total, + comment=f"Weighted average of {len(evaluations)} metrics" + ) + + result = client.run_batched_evaluation( + scope="traces", + mapper=trace_mapper, + evaluators=[accuracy_evaluator, relevance_evaluator], + composite_evaluator=composite_evaluator, + filter='{"user_id": "important_user"}', + verbose=True + ) + ``` + + Handling incomplete runs with resume: + ```python + # Initial run that may fail or timeout + result = client.run_batched_evaluation( + scope="observations", + mapper=obs_mapper, + evaluators=[my_evaluator], + max_items=10000, + verbose=True + ) + + # Check if incomplete + if not result.completed and result.resume_token: + print(f"Processed {result.resume_token.items_processed} items before interruption") + + # Resume from where it left off + result = client.run_batched_evaluation( + scope="observations", + mapper=obs_mapper, + evaluators=[my_evaluator], + resume_from=result.resume_token, + verbose=True + ) + + print(f"Total items processed: {result.total_items_processed}") + ``` + + Monitoring evaluator performance: + ```python + result = client.run_batched_evaluation(...) + + for stats in result.evaluator_stats: + success_rate = stats.successful_runs / stats.total_runs + print(f"{stats.name}:") + print(f" Success rate: {success_rate:.1%}") + print(f" Scores created: {stats.total_scores_created}") + + if stats.failed_runs > 0: + print(f" ⚠️ Failed {stats.failed_runs} times") + ``` + + Note: + - Evaluator failures are logged but don't stop the batch evaluation + - Individual item failures are tracked but don't stop processing + - Fetch failures are retried with exponential backoff + - All scores are automatically flushed to Langfuse at the end + - The resume mechanism uses timestamp-based filtering to avoid duplicates + """ + runner = BatchEvaluationRunner(self) + + return cast( + BatchEvaluationResult, + run_async_safely( + runner.run_async( + scope=scope, + mapper=mapper, + evaluators=evaluators, + filter=filter, + fetch_batch_size=fetch_batch_size, + max_items=max_items, + max_concurrency=max_concurrency, + composite_evaluator=composite_evaluator, + metadata=metadata, + max_retries=max_retries, + verbose=verbose, + resume_from=resume_from, + ) + ), + ) + def auth_check(self) -> bool: """Check if the provided credentials (public and secret key) are valid. diff --git a/langfuse/_client/datasets.py b/langfuse/_client/datasets.py index beb1248ba..0a9a0312c 100644 --- a/langfuse/_client/datasets.py +++ b/langfuse/_client/datasets.py @@ -4,6 +4,7 @@ from opentelemetry.util._decorator import _agnosticcontextmanager +from langfuse.batch_evaluation import CompositeEvaluatorFunction from langfuse.experiment import ( EvaluatorFunction, ExperimentResult, @@ -204,6 +205,7 @@ def run_experiment( description: Optional[str] = None, task: TaskFunction, evaluators: List[EvaluatorFunction] = [], + composite_evaluator: Optional[CompositeEvaluatorFunction] = None, run_evaluators: List[RunEvaluatorFunction] = [], max_concurrency: int = 50, metadata: Optional[Dict[str, Any]] = None, @@ -234,6 +236,10 @@ def run_experiment( .metadata attributes. Signature should be: task(*, item, **kwargs) -> Any evaluators: List of functions to evaluate each item's output individually. These will have access to the item's expected_output for comparison. + composite_evaluator: Optional function that creates composite scores from item-level evaluations. + Receives the same inputs as item-level evaluators (input, output, expected_output, metadata) + plus the list of evaluations from item-level evaluators. Useful for weighted averages, + pass/fail decisions based on multiple criteria, or custom scoring logic combining multiple metrics. run_evaluators: List of functions to evaluate the entire experiment run. Useful for computing aggregate statistics across all dataset items. max_concurrency: Maximum number of concurrent task executions (default: 50). @@ -411,6 +417,7 @@ def content_diversity(*, item_results, **kwargs): data=self.items, task=task, evaluators=evaluators, + composite_evaluator=composite_evaluator, run_evaluators=run_evaluators, max_concurrency=max_concurrency, metadata=metadata, diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index afd969201..e8786a0e0 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -589,7 +589,9 @@ def __next__(self) -> Any: raise # Re-raise StopIteration except Exception as e: - self.span.update(level="ERROR", status_message=str(e) or type(e).__name__).end() + self.span.update( + level="ERROR", status_message=str(e) or type(e).__name__ + ).end() raise @@ -654,6 +656,8 @@ async def __anext__(self) -> Any: raise # Re-raise StopAsyncIteration except Exception as e: - self.span.update(level="ERROR", status_message=str(e) or type(e).__name__).end() + self.span.update( + level="ERROR", status_message=str(e) or type(e).__name__ + ).end() raise diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py new file mode 100644 index 000000000..35e1ea938 --- /dev/null +++ b/langfuse/batch_evaluation.py @@ -0,0 +1,1558 @@ +"""Batch evaluation functionality for Langfuse. + +This module provides comprehensive batch evaluation capabilities for running evaluations +on traces and observations fetched from Langfuse. It includes type definitions, +protocols, result classes, and the implementation for large-scale evaluation workflows +with error handling, retry logic, and resume capability. +""" + +import asyncio +import json +import logging +import time +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Dict, + List, + Optional, + Protocol, + Tuple, + Union, +) + +from langfuse.api.resources.commons.types import ( + ObservationsView, + TraceWithFullDetails, +) +from langfuse.experiment import Evaluation, EvaluatorFunction + +if TYPE_CHECKING: + from langfuse._client.client import Langfuse + +logger = logging.getLogger("langfuse") + + +class EvaluatorInputs: + """Input data structure for evaluators, returned by mapper functions. + + This class provides a strongly-typed container for transforming API response + objects (traces, observations) into the standardized format expected + by evaluator functions. It ensures consistent access to input, output, expected + output, and metadata regardless of the source entity type. + + Attributes: + input: The input data that was provided to generate the output being evaluated. + For traces, this might be the initial prompt or request. For observations, + this could be the span's input. The exact meaning depends on your use case. + output: The actual output that was produced and needs to be evaluated. + For traces, this is typically the final response. For observations, + this might be the generation output or span result. + expected_output: Optional ground truth or expected result for comparison. + Used by evaluators to assess correctness. May be None if no ground truth + is available for the entity being evaluated. + metadata: Optional structured metadata providing additional context for evaluation. + Can include information about the entity, execution context, user attributes, + or any other relevant data that evaluators might use. + + Examples: + Simple mapper for traces: + ```python + from langfuse import EvaluatorInputs + + def trace_mapper(trace): + return EvaluatorInputs( + input=trace.input, + output=trace.output, + expected_output=None, # No ground truth available + metadata={"user_id": trace.user_id, "tags": trace.tags} + ) + ``` + + Mapper for observations extracting specific fields: + ```python + def observation_mapper(observation): + # Extract input/output from observation's data + input_data = observation.input if hasattr(observation, 'input') else None + output_data = observation.output if hasattr(observation, 'output') else None + + return EvaluatorInputs( + input=input_data, + output=output_data, + expected_output=None, + metadata={ + "observation_type": observation.type, + "model": observation.model, + "latency_ms": observation.end_time - observation.start_time + } + ) + ``` + ``` + + Note: + All arguments must be passed as keywords when instantiating this class. + """ + + def __init__( + self, + *, + input: Any, + output: Any, + expected_output: Any = None, + metadata: Optional[Dict[str, Any]] = None, + ): + """Initialize EvaluatorInputs with the provided data. + + Args: + input: The input data for evaluation. + output: The output data to be evaluated. + expected_output: Optional ground truth for comparison. + metadata: Optional additional context for evaluation. + + Note: + All arguments must be provided as keywords. + """ + self.input = input + self.output = output + self.expected_output = expected_output + self.metadata = metadata + + +class MapperFunction(Protocol): + """Protocol defining the interface for mapper functions in batch evaluation. + + Mapper functions transform API response objects (traces or observations) + into the standardized EvaluatorInputs format that evaluators expect. This abstraction + allows you to define how to extract and structure evaluation data from different + entity types. + + Mapper functions must: + - Accept a single item parameter (trace, observation) + - Return an EvaluatorInputs instance with input, output, expected_output, metadata + - Can be either synchronous or asynchronous + - Should handle missing or malformed data gracefully + """ + + def __call__( + self, + *, + item: Union["TraceWithFullDetails", "ObservationsView"], + **kwargs: Dict[str, Any], + ) -> Union[EvaluatorInputs, Awaitable[EvaluatorInputs]]: + """Transform an API response object into evaluator inputs. + + This method defines how to extract evaluation-relevant data from the raw + API response object. The implementation should map entity-specific fields + to the standardized input/output/expected_output/metadata structure. + + Args: + item: The API response object to transform. The type depends on the scope: + - TraceWithFullDetails: When evaluating traces + - ObservationsView: When evaluating observations + + Returns: + EvaluatorInputs: A structured container with: + - input: The input data that generated the output + - output: The output to be evaluated + - expected_output: Optional ground truth for comparison + - metadata: Optional additional context + + Can return either a direct EvaluatorInputs instance or an awaitable + (for async mappers that need to fetch additional data). + + Examples: + Basic trace mapper: + ```python + def map_trace(trace): + return EvaluatorInputs( + input=trace.input, + output=trace.output, + expected_output=None, + metadata={"trace_id": trace.id, "user": trace.user_id} + ) + ``` + + Observation mapper with conditional logic: + ```python + def map_observation(observation): + # Extract fields based on observation type + if observation.type == "GENERATION": + input_data = observation.input + output_data = observation.output + else: + # For other types, use different fields + input_data = observation.metadata.get("input") + output_data = observation.metadata.get("output") + + return EvaluatorInputs( + input=input_data, + output=output_data, + expected_output=None, + metadata={"obs_id": observation.id, "type": observation.type} + ) + ``` + + Async mapper (if additional processing needed): + ```python + async def map_trace_async(trace): + # Could do async processing here if needed + processed_output = await some_async_transformation(trace.output) + + return EvaluatorInputs( + input=trace.input, + output=processed_output, + expected_output=None, + metadata={"trace_id": trace.id} + ) + ``` + """ + ... + + +class CompositeEvaluatorFunction(Protocol): + """Protocol defining the interface for composite evaluator functions. + + Composite evaluators create aggregate scores from multiple item-level evaluations. + This is commonly used to compute weighted averages, combined metrics, or other + composite assessments based on individual evaluation results. + + Composite evaluators: + - Accept the same inputs as item-level evaluators (input, output, expected_output, metadata) + plus the list of evaluations + - Return either a single Evaluation, a list of Evaluations, or a dict + - Can be either synchronous or asynchronous + - Have access to both raw item data and evaluation results + """ + + def __call__( + self, + *, + input: Optional[Any] = None, + output: Optional[Any] = None, + expected_output: Optional[Any] = None, + metadata: Optional[Dict[str, Any]] = None, + evaluations: List[Evaluation], + **kwargs: Dict[str, Any], + ) -> Union[ + Evaluation, + List[Evaluation], + Dict[str, Any], + Awaitable[Evaluation], + Awaitable[List[Evaluation]], + Awaitable[Dict[str, Any]], + ]: + r"""Create a composite evaluation from item-level evaluation results. + + This method combines multiple evaluation scores into a single composite metric. + Common use cases include weighted averages, pass/fail decisions based on multiple + criteria, or custom scoring logic that considers multiple dimensions. + + Args: + input: The input data that was provided to the system being evaluated. + output: The output generated by the system being evaluated. + expected_output: The expected/reference output for comparison (if available). + metadata: Additional metadata about the evaluation context. + evaluations: List of evaluation results from item-level evaluators. + Each evaluation contains name, value, comment, and metadata. + + Returns: + Can return any of: + - Evaluation: A single composite evaluation result + - List[Evaluation]: Multiple composite evaluations + - Dict: A dict that will be converted to an Evaluation + - name: Identifier for the composite metric (e.g., "composite_score") + - value: The computed composite value + - comment: Optional explanation of how the score was computed + - metadata: Optional details about the composition logic + + Can return either a direct Evaluation instance or an awaitable + (for async composite evaluators). + + Examples: + Simple weighted average: + ```python + def weighted_composite(*, input, output, expected_output, metadata, evaluations): + weights = { + "accuracy": 0.5, + "relevance": 0.3, + "safety": 0.2 + } + + total_score = 0.0 + total_weight = 0.0 + + for eval in evaluations: + if eval.name in weights and isinstance(eval.value, (int, float)): + total_score += eval.value * weights[eval.name] + total_weight += weights[eval.name] + + final_score = total_score / total_weight if total_weight > 0 else 0.0 + + return Evaluation( + name="composite_score", + value=final_score, + comment=f"Weighted average of {len(evaluations)} metrics" + ) + ``` + + Pass/fail composite based on thresholds: + ```python + def pass_fail_composite(*, input, output, expected_output, metadata, evaluations): + # Must pass all criteria + thresholds = { + "accuracy": 0.7, + "safety": 0.9, + "relevance": 0.6 + } + + passes = True + failing_metrics = [] + + for metric, threshold in thresholds.items(): + eval_result = next((e for e in evaluations if e.name == metric), None) + if eval_result and isinstance(eval_result.value, (int, float)): + if eval_result.value < threshold: + passes = False + failing_metrics.append(metric) + + return Evaluation( + name="passes_all_checks", + value=passes, + comment=f"Failed: {', '.join(failing_metrics)}" if failing_metrics else "All checks passed", + data_type="BOOLEAN" + ) + ``` + + Async composite with external scoring: + ```python + async def llm_composite(*, input, output, expected_output, metadata, evaluations): + # Use LLM to synthesize multiple evaluation results + eval_summary = "\n".join( + f"- {e.name}: {e.value}" for e in evaluations + ) + + prompt = f"Given these evaluation scores:\n{eval_summary}\n" + prompt += f"For the output: {output}\n" + prompt += "Provide an overall quality score from 0-1." + + response = await openai.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": prompt}] + ) + + score = float(response.choices[0].message.content.strip()) + + return Evaluation( + name="llm_composite_score", + value=score, + comment="LLM-synthesized composite score" + ) + ``` + + Context-aware composite: + ```python + def context_composite(*, input, output, expected_output, metadata, evaluations): + # Adjust weighting based on metadata + base_weights = {"accuracy": 0.5, "speed": 0.3, "cost": 0.2} + + # If metadata indicates high importance, prioritize accuracy + if metadata and metadata.get('importance') == 'high': + weights = {"accuracy": 0.7, "speed": 0.2, "cost": 0.1} + else: + weights = base_weights + + total = sum( + e.value * weights.get(e.name, 0) + for e in evaluations + if isinstance(e.value, (int, float)) + ) + + return Evaluation( + name="weighted_composite", + value=total, + comment="Context-aware weighted composite" + ) + ``` + """ + ... + + +class EvaluatorStats: + """Statistics for a single evaluator's performance during batch evaluation. + + This class tracks detailed metrics about how a specific evaluator performed + across all items in a batch evaluation run. It helps identify evaluator issues, + understand reliability, and optimize evaluation pipelines. + + Attributes: + name: The name of the evaluator function (extracted from __name__). + total_runs: Total number of times the evaluator was invoked. + successful_runs: Number of times the evaluator completed successfully. + failed_runs: Number of times the evaluator raised an exception or failed. + total_scores_created: Total number of evaluation scores created by this evaluator. + Can be higher than successful_runs if the evaluator returns multiple scores. + + Examples: + Accessing evaluator stats from batch evaluation result: + ```python + result = client.run_batched_evaluation(...) + + for stats in result.evaluator_stats: + print(f"Evaluator: {stats.name}") + print(f" Success rate: {stats.successful_runs / stats.total_runs:.1%}") + print(f" Scores created: {stats.total_scores_created}") + + if stats.failed_runs > 0: + print(f" ⚠️ Failed {stats.failed_runs} times") + ``` + + Identifying problematic evaluators: + ```python + result = client.run_batched_evaluation(...) + + # Find evaluators with high failure rates + for stats in result.evaluator_stats: + failure_rate = stats.failed_runs / stats.total_runs + if failure_rate > 0.1: # More than 10% failures + print(f"⚠️ {stats.name} has {failure_rate:.1%} failure rate") + print(f" Consider debugging or removing this evaluator") + ``` + + Note: + All arguments must be passed as keywords when instantiating this class. + """ + + def __init__( + self, + *, + name: str, + total_runs: int = 0, + successful_runs: int = 0, + failed_runs: int = 0, + total_scores_created: int = 0, + ): + """Initialize EvaluatorStats with the provided metrics. + + Args: + name: The evaluator function name. + total_runs: Total number of evaluator invocations. + successful_runs: Number of successful completions. + failed_runs: Number of failures. + total_scores_created: Total scores created by this evaluator. + + Note: + All arguments must be provided as keywords. + """ + self.name = name + self.total_runs = total_runs + self.successful_runs = successful_runs + self.failed_runs = failed_runs + self.total_scores_created = total_scores_created + + +class BatchEvaluationResumeToken: + """Token for resuming a failed batch evaluation run. + + This class encapsulates all the information needed to resume a batch evaluation + that was interrupted or failed partway through. It uses timestamp-based filtering + to avoid re-processing items that were already evaluated, even if the underlying + dataset changed between runs. + + Attributes: + scope: The type of items being evaluated ("traces", "observations"). + filter: The original JSON filter string used to query items. + last_processed_timestamp: ISO 8601 timestamp of the last successfully processed item. + Used to construct a filter that only fetches items after this timestamp. + last_processed_id: The ID of the last successfully processed item, for reference. + items_processed: Count of items successfully processed before interruption. + + Examples: + Resuming a failed batch evaluation: + ```python + # Initial run that fails partway through + try: + result = client.run_batched_evaluation( + scope="traces", + mapper=my_mapper, + evaluators=[evaluator1, evaluator2], + filter='{"tags": ["production"]}', + max_items=10000 + ) + except Exception as e: + print(f"Evaluation failed: {e}") + + # Save the resume token + if result.resume_token: + # Store resume token for later (e.g., in a file or database) + import json + with open("resume_token.json", "w") as f: + json.dump({ + "scope": result.resume_token.scope, + "filter": result.resume_token.filter, + "last_timestamp": result.resume_token.last_processed_timestamp, + "last_id": result.resume_token.last_processed_id, + "items_done": result.resume_token.items_processed + }, f) + + # Later, resume from where it left off + with open("resume_token.json") as f: + token_data = json.load(f) + + resume_token = BatchEvaluationResumeToken( + scope=token_data["scope"], + filter=token_data["filter"], + last_processed_timestamp=token_data["last_timestamp"], + last_processed_id=token_data["last_id"], + items_processed=token_data["items_done"] + ) + + # Resume the evaluation + result = client.run_batched_evaluation( + scope="traces", + mapper=my_mapper, + evaluators=[evaluator1, evaluator2], + resume_from=resume_token + ) + + print(f"Processed {result.total_items_processed} additional items") + ``` + + Handling partial completion: + ```python + result = client.run_batched_evaluation(...) + + if not result.completed: + print(f"Evaluation incomplete. Processed {result.resume_token.items_processed} items") + print(f"Last item: {result.resume_token.last_processed_id}") + print(f"Resume from: {result.resume_token.last_processed_timestamp}") + + # Optionally retry automatically + if result.resume_token: + print("Retrying...") + result = client.run_batched_evaluation( + scope=result.resume_token.scope, + mapper=my_mapper, + evaluators=my_evaluators, + resume_from=result.resume_token + ) + ``` + + Note: + All arguments must be passed as keywords when instantiating this class. + The timestamp-based approach means that items created after the initial run + but before the timestamp will be skipped. This is intentional to avoid + duplicates and ensure consistent evaluation. + """ + + def __init__( + self, + *, + scope: str, + filter: Optional[str], + last_processed_timestamp: str, + last_processed_id: str, + items_processed: int, + ): + """Initialize BatchEvaluationResumeToken with the provided state. + + Args: + scope: The scope type ("traces", "observations"). + filter: The original JSON filter string. + last_processed_timestamp: ISO 8601 timestamp of last processed item. + last_processed_id: ID of last processed item. + items_processed: Count of items processed before interruption. + + Note: + All arguments must be provided as keywords. + """ + self.scope = scope + self.filter = filter + self.last_processed_timestamp = last_processed_timestamp + self.last_processed_id = last_processed_id + self.items_processed = items_processed + + +class BatchEvaluationResult: + r"""Complete result structure for batch evaluation execution. + + This class encapsulates comprehensive statistics and metadata about a batch + evaluation run, including counts, evaluator-specific metrics, timing information, + error details, and resume capability. + + Attributes: + total_items_fetched: Total number of items fetched from the API. + total_items_processed: Number of items successfully evaluated. + total_items_failed: Number of items that failed during evaluation. + total_scores_created: Total scores created by all item-level evaluators. + total_composite_scores_created: Scores created by the composite evaluator. + total_evaluations_failed: Number of individual evaluator failures across all items. + evaluator_stats: List of per-evaluator statistics (success/failure rates, scores created). + resume_token: Token for resuming if evaluation was interrupted (None if completed). + completed: True if all items were processed, False if stopped early or failed. + duration_seconds: Total time taken to execute the batch evaluation. + failed_item_ids: List of IDs for items that failed evaluation. + error_summary: Dictionary mapping error types to occurrence counts. + has_more_items: True if max_items limit was reached but more items exist. + item_evaluations: Dictionary mapping item IDs to their evaluation results (both regular and composite). + + Examples: + Basic result inspection: + ```python + result = client.run_batched_evaluation(...) + + print(f"Processed: {result.total_items_processed}/{result.total_items_fetched}") + print(f"Scores created: {result.total_scores_created}") + print(f"Duration: {result.duration_seconds:.2f}s") + print(f"Success rate: {result.total_items_processed / result.total_items_fetched:.1%}") + ``` + + Detailed analysis with evaluator stats: + ```python + result = client.run_batched_evaluation(...) + + print(f"\n📊 Batch Evaluation Results") + print(f"{'='*50}") + print(f"Items processed: {result.total_items_processed}") + print(f"Items failed: {result.total_items_failed}") + print(f"Scores created: {result.total_scores_created}") + + if result.total_composite_scores_created > 0: + print(f"Composite scores: {result.total_composite_scores_created}") + + print(f"\n📈 Evaluator Performance:") + for stats in result.evaluator_stats: + success_rate = stats.successful_runs / stats.total_runs if stats.total_runs > 0 else 0 + print(f"\n {stats.name}:") + print(f" Success rate: {success_rate:.1%}") + print(f" Scores created: {stats.total_scores_created}") + if stats.failed_runs > 0: + print(f" ⚠️ Failures: {stats.failed_runs}") + + if result.error_summary: + print(f"\n⚠️ Errors encountered:") + for error_type, count in result.error_summary.items(): + print(f" {error_type}: {count}") + ``` + + Handling incomplete runs: + ```python + result = client.run_batched_evaluation(...) + + if not result.completed: + print("⚠️ Evaluation incomplete!") + + if result.resume_token: + print(f"Processed {result.resume_token.items_processed} items before failure") + print(f"Use resume_from parameter to continue from:") + print(f" Timestamp: {result.resume_token.last_processed_timestamp}") + print(f" Last ID: {result.resume_token.last_processed_id}") + + if result.has_more_items: + print(f"ℹ️ More items available beyond max_items limit") + ``` + + Performance monitoring: + ```python + result = client.run_batched_evaluation(...) + + items_per_second = result.total_items_processed / result.duration_seconds + avg_scores_per_item = result.total_scores_created / result.total_items_processed + + print(f"Performance metrics:") + print(f" Throughput: {items_per_second:.2f} items/second") + print(f" Avg scores/item: {avg_scores_per_item:.2f}") + print(f" Total duration: {result.duration_seconds:.2f}s") + + if result.total_evaluations_failed > 0: + failure_rate = result.total_evaluations_failed / ( + result.total_items_processed * len(result.evaluator_stats) + ) + print(f" Evaluation failure rate: {failure_rate:.1%}") + ``` + + Note: + All arguments must be passed as keywords when instantiating this class. + """ + + def __init__( + self, + *, + total_items_fetched: int, + total_items_processed: int, + total_items_failed: int, + total_scores_created: int, + total_composite_scores_created: int, + total_evaluations_failed: int, + evaluator_stats: List[EvaluatorStats], + resume_token: Optional[BatchEvaluationResumeToken], + completed: bool, + duration_seconds: float, + failed_item_ids: List[str], + error_summary: Dict[str, int], + has_more_items: bool, + item_evaluations: Dict[str, List["Evaluation"]], + ): + """Initialize BatchEvaluationResult with comprehensive statistics. + + Args: + total_items_fetched: Total items fetched from API. + total_items_processed: Items successfully evaluated. + total_items_failed: Items that failed evaluation. + total_scores_created: Scores from item-level evaluators. + total_composite_scores_created: Scores from composite evaluator. + total_evaluations_failed: Individual evaluator failures. + evaluator_stats: Per-evaluator statistics. + resume_token: Token for resuming (None if completed). + completed: Whether all items were processed. + duration_seconds: Total execution time. + failed_item_ids: IDs of failed items. + error_summary: Error types and counts. + has_more_items: Whether more items exist beyond max_items. + item_evaluations: Dictionary mapping item IDs to their evaluation results. + + Note: + All arguments must be provided as keywords. + """ + self.total_items_fetched = total_items_fetched + self.total_items_processed = total_items_processed + self.total_items_failed = total_items_failed + self.total_scores_created = total_scores_created + self.total_composite_scores_created = total_composite_scores_created + self.total_evaluations_failed = total_evaluations_failed + self.evaluator_stats = evaluator_stats + self.resume_token = resume_token + self.completed = completed + self.duration_seconds = duration_seconds + self.failed_item_ids = failed_item_ids + self.error_summary = error_summary + self.has_more_items = has_more_items + self.item_evaluations = item_evaluations + + def __str__(self) -> str: + """Return a formatted string representation of the batch evaluation results. + + Returns: + A multi-line string with a summary of the evaluation results. + """ + lines = [] + lines.append("=" * 60) + lines.append("Batch Evaluation Results") + lines.append("=" * 60) + + # Summary statistics + lines.append(f"\nStatus: {'Completed' if self.completed else 'Incomplete'}") + lines.append(f"Duration: {self.duration_seconds:.2f}s") + lines.append(f"\nItems fetched: {self.total_items_fetched}") + lines.append(f"Items processed: {self.total_items_processed}") + + if self.total_items_failed > 0: + lines.append(f"Items failed: {self.total_items_failed}") + + # Success rate + if self.total_items_fetched > 0: + success_rate = self.total_items_processed / self.total_items_fetched * 100 + lines.append(f"Success rate: {success_rate:.1f}%") + + # Scores created + lines.append(f"\nScores created: {self.total_scores_created}") + if self.total_composite_scores_created > 0: + lines.append(f"Composite scores: {self.total_composite_scores_created}") + + total_scores = self.total_scores_created + self.total_composite_scores_created + lines.append(f"Total scores: {total_scores}") + + # Evaluator statistics + if self.evaluator_stats: + lines.append("\nEvaluator Performance:") + for stats in self.evaluator_stats: + lines.append(f" {stats.name}:") + if stats.total_runs > 0: + success_rate = ( + stats.successful_runs / stats.total_runs * 100 + if stats.total_runs > 0 + else 0 + ) + lines.append( + f" Runs: {stats.successful_runs}/{stats.total_runs} " + f"({success_rate:.1f}% success)" + ) + lines.append(f" Scores created: {stats.total_scores_created}") + if stats.failed_runs > 0: + lines.append(f" Failed runs: {stats.failed_runs}") + + # Performance metrics + if self.total_items_processed > 0 and self.duration_seconds > 0: + items_per_sec = self.total_items_processed / self.duration_seconds + lines.append("\nPerformance:") + lines.append(f" Throughput: {items_per_sec:.2f} items/second") + if self.total_scores_created > 0: + avg_scores = self.total_scores_created / self.total_items_processed + lines.append(f" Avg scores per item: {avg_scores:.2f}") + + # Errors and warnings + if self.error_summary: + lines.append("\nErrors encountered:") + for error_type, count in self.error_summary.items(): + lines.append(f" {error_type}: {count}") + + # Incomplete run information + if not self.completed: + lines.append("\nWarning: Evaluation incomplete") + if self.resume_token: + lines.append( + f" Last processed: {self.resume_token.last_processed_timestamp}" + ) + lines.append(f" Items processed: {self.resume_token.items_processed}") + lines.append(" Use resume_from parameter to continue") + + if self.has_more_items: + lines.append("\nNote: More items available beyond max_items limit") + + lines.append("=" * 60) + return "\n".join(lines) + + +class BatchEvaluationRunner: + """Handles batch evaluation execution for a Langfuse client. + + This class encapsulates all the logic for fetching items, running evaluators, + creating scores, and managing the evaluation lifecycle. It provides a clean + separation of concerns from the main Langfuse client class. + + The runner uses a streaming/pipeline approach to process items in batches, + avoiding loading the entire dataset into memory. This makes it suitable for + evaluating large numbers of items. + + Attributes: + client: The Langfuse client instance used for API calls and score creation. + _log: Logger instance for this runner. + """ + + def __init__(self, client: "Langfuse"): + """Initialize the batch evaluation runner. + + Args: + client: The Langfuse client instance. + """ + self.client = client + self._log = logger + + async def run_async( + self, + *, + scope: str, + mapper: MapperFunction, + evaluators: List[EvaluatorFunction], + filter: Optional[str] = None, + fetch_batch_size: int = 50, + max_items: Optional[int] = None, + max_concurrency: int = 50, + composite_evaluator: Optional[CompositeEvaluatorFunction] = None, + metadata: Optional[Dict[str, Any]] = None, + max_retries: int = 3, + verbose: bool = False, + resume_from: Optional[BatchEvaluationResumeToken] = None, + ) -> BatchEvaluationResult: + """Run batch evaluation asynchronously. + + This is the main implementation method that orchestrates the entire batch + evaluation process: fetching items, mapping, evaluating, creating scores, + and tracking statistics. + + Args: + scope: The type of items to evaluate ("traces", "observations"). + mapper: Function to transform API response items to evaluator inputs. + evaluators: List of evaluation functions to run on each item. + filter: JSON filter string for querying items. + fetch_batch_size: Number of items to fetch per API call. + max_items: Maximum number of items to process (None = all). + max_concurrency: Maximum number of concurrent evaluations. + composite_evaluator: Optional function to create composite scores. + metadata: Metadata to add to all created scores. + max_retries: Maximum retries for failed batch fetches. + verbose: If True, log progress to console. + resume_from: Resume token from a previous failed run. + + Returns: + BatchEvaluationResult with comprehensive statistics. + """ + start_time = time.time() + + # Initialize tracking variables + total_items_fetched = 0 + total_items_processed = 0 + total_items_failed = 0 + total_scores_created = 0 + total_composite_scores_created = 0 + total_evaluations_failed = 0 + failed_item_ids: List[str] = [] + error_summary: Dict[str, int] = {} + item_evaluations: Dict[str, List[Evaluation]] = {} + + # Initialize evaluator stats + evaluator_stats_dict = { + getattr(evaluator, "__name__", "unknown_evaluator"): EvaluatorStats( + name=getattr(evaluator, "__name__", "unknown_evaluator") + ) + for evaluator in evaluators + } + + # Handle resume token by modifying filter + effective_filter = self._build_timestamp_filter(filter, resume_from) + + # Create semaphore for concurrency control + semaphore = asyncio.Semaphore(max_concurrency) + + # Pagination state + page = 1 + has_more = True + last_item_timestamp: Optional[str] = None + last_item_id: Optional[str] = None + + if verbose: + self._log.info(f"Starting batch evaluation on {scope}") + if resume_from: + self._log.info( + f"Resuming from {resume_from.last_processed_timestamp} " + f"({resume_from.items_processed} items already processed)" + ) + + # Main pagination loop + while has_more: + # Check if we've reached max_items + if max_items is not None and total_items_fetched >= max_items: + if verbose: + self._log.info(f"Reached max_items limit ({max_items})") + has_more = True # More items may exist + break + + # Fetch next batch with retry logic + try: + items = await self._fetch_batch_with_retry( + scope=scope, + filter=effective_filter, + page=page, + limit=fetch_batch_size, + max_retries=max_retries, + ) + except Exception as e: + # Failed after max_retries - create resume token and return + error_msg = f"Failed to fetch batch after {max_retries} retries" + self._log.error(f"{error_msg}: {e}") + + resume_token = BatchEvaluationResumeToken( + scope=scope, + filter=filter, # Original filter, not modified + last_processed_timestamp=last_item_timestamp or "", + last_processed_id=last_item_id or "", + items_processed=total_items_processed, + ) + + return self._build_result( + total_items_fetched=total_items_fetched, + total_items_processed=total_items_processed, + total_items_failed=total_items_failed, + total_scores_created=total_scores_created, + total_composite_scores_created=total_composite_scores_created, + total_evaluations_failed=total_evaluations_failed, + evaluator_stats_dict=evaluator_stats_dict, + resume_token=resume_token, + completed=False, + start_time=start_time, + failed_item_ids=failed_item_ids, + error_summary=error_summary, + has_more_items=has_more, + item_evaluations=item_evaluations, + ) + + # Check if we got any items + if not items: + has_more = False + if verbose: + self._log.info("No more items to fetch") + break + + total_items_fetched += len(items) + + if verbose: + self._log.info(f"Fetched batch {page} ({len(items)} items)") + + # Limit items if max_items would be exceeded + items_to_process = items + if max_items is not None: + remaining_capacity = max_items - total_items_processed + if len(items) > remaining_capacity: + items_to_process = items[:remaining_capacity] + if verbose: + self._log.info( + f"Limiting batch to {len(items_to_process)} items " + f"to respect max_items={max_items}" + ) + + # Process items concurrently + async def process_item( + item: Union[TraceWithFullDetails, ObservationsView], + ) -> Tuple[str, Union[Tuple[int, int, int, List[Evaluation]], Exception]]: + """Process a single item and return (item_id, result).""" + async with semaphore: + item_id = self._get_item_id(item, scope) + try: + result = await self._process_batch_evaluation_item( + item=item, + scope=scope, + mapper=mapper, + evaluators=evaluators, + composite_evaluator=composite_evaluator, + metadata=metadata, + evaluator_stats_dict=evaluator_stats_dict, + ) + return (item_id, result) + except Exception as e: + return (item_id, e) + + # Run all items in batch concurrently + tasks = [process_item(item) for item in items_to_process] + results = await asyncio.gather(*tasks) + + # Process results and update statistics + for item, (item_id, result) in zip(items_to_process, results): + if isinstance(result, Exception): + # Item processing failed + total_items_failed += 1 + failed_item_ids.append(item_id) + error_type = type(result).__name__ + error_summary[error_type] = error_summary.get(error_type, 0) + 1 + self._log.warning(f"Item {item_id} failed: {result}") + else: + # Item processed successfully + total_items_processed += 1 + scores_created, composite_created, evals_failed, evaluations = ( + result + ) + total_scores_created += scores_created + total_composite_scores_created += composite_created + total_evaluations_failed += evals_failed + + # Store evaluations for this item + item_evaluations[item_id] = evaluations + + # Update last processed tracking + last_item_timestamp = self._get_item_timestamp(item, scope) + last_item_id = item_id + + if verbose: + if max_items is not None and max_items > 0: + progress_pct = total_items_processed / max_items * 100 + self._log.info( + f"Progress: {total_items_processed}/{max_items} items " + f"({progress_pct:.1f}%), {total_scores_created} scores created" + ) + else: + self._log.info( + f"Progress: {total_items_processed} items processed, " + f"{total_scores_created} scores created" + ) + + # Check if we should continue to next page + if len(items) < fetch_batch_size: + # Last page - no more items available + has_more = False + else: + page += 1 + + # Check max_items again before next fetch + if max_items is not None and total_items_fetched >= max_items: + has_more = True # More items exist but we're stopping + break + + # Flush all scores to Langfuse + if verbose: + self._log.info("Flushing scores to Langfuse...") + self.client.flush() + + # Build final result + duration = time.time() - start_time + + if verbose: + self._log.info( + f"Batch evaluation complete: {total_items_processed} items processed " + f"in {duration:.2f}s" + ) + + # Completed successfully if we either: + # 1. Ran out of items (has_more is False), OR + # 2. Hit max_items limit (intentionally stopped) + completed_successfully = not has_more or ( + max_items is not None and total_items_fetched >= max_items + ) + + return self._build_result( + total_items_fetched=total_items_fetched, + total_items_processed=total_items_processed, + total_items_failed=total_items_failed, + total_scores_created=total_scores_created, + total_composite_scores_created=total_composite_scores_created, + total_evaluations_failed=total_evaluations_failed, + evaluator_stats_dict=evaluator_stats_dict, + resume_token=None, # No resume needed on successful completion + completed=completed_successfully, + start_time=start_time, + failed_item_ids=failed_item_ids, + error_summary=error_summary, + has_more_items=( + has_more and max_items is not None and total_items_fetched >= max_items + ), + item_evaluations=item_evaluations, + ) + + async def _fetch_batch_with_retry( + self, + *, + scope: str, + filter: Optional[str], + page: int, + limit: int, + max_retries: int, + ) -> List[Union[TraceWithFullDetails, ObservationsView]]: + """Fetch a batch of items with retry logic. + + Args: + scope: The type of items ("traces", "observations"). + filter: JSON filter string for querying. + page: Page number (1-indexed). + limit: Number of items per page. + max_retries: Maximum number of retry attempts. + verbose: Whether to log retry attempts. + + Returns: + List of items from the API. + + Raises: + Exception: If all retry attempts fail. + """ + if scope == "traces": + response = self.client.api.trace.list( + page=page, + limit=limit, + filter=filter, + request_options={"max_retries": max_retries}, + ) # type: ignore + return list(response.data) # type: ignore + elif scope == "observations": + response = self.client.api.observations.get_many( + page=page, + limit=limit, + filter=filter, + request_options={"max_retries": max_retries}, + ) # type: ignore + return list(response.data) # type: ignore + else: + error_message = f"Invalid scope: {scope}" + raise ValueError(error_message) + + async def _process_batch_evaluation_item( + self, + item: Union[TraceWithFullDetails, ObservationsView], + scope: str, + mapper: MapperFunction, + evaluators: List[EvaluatorFunction], + composite_evaluator: Optional[CompositeEvaluatorFunction], + metadata: Optional[Dict[str, Any]], + evaluator_stats_dict: Dict[str, EvaluatorStats], + ) -> Tuple[int, int, int, List[Evaluation]]: + """Process a single item: map, evaluate, create scores. + + Args: + item: The API response object to evaluate. + scope: The type of item ("traces", "observations"). + mapper: Function to transform item to evaluator inputs. + evaluators: List of evaluator functions. + composite_evaluator: Optional composite evaluator function. + metadata: Additional metadata to add to scores. + evaluator_stats_dict: Dictionary tracking evaluator statistics. + + Returns: + Tuple of (scores_created, composite_scores_created, evaluations_failed, all_evaluations). + + Raises: + Exception: If mapping fails or item processing encounters fatal error. + """ + scores_created = 0 + composite_scores_created = 0 + evaluations_failed = 0 + + # Run mapper to transform item + evaluator_inputs = await self._run_mapper(mapper, item) + + # Run all evaluators + evaluations: List[Evaluation] = [] + for evaluator in evaluators: + evaluator_name = getattr(evaluator, "__name__", "unknown_evaluator") + stats = evaluator_stats_dict[evaluator_name] + stats.total_runs += 1 + + try: + eval_results = await self._run_evaluator_internal( + evaluator, + input=evaluator_inputs.input, + output=evaluator_inputs.output, + expected_output=evaluator_inputs.expected_output, + metadata=evaluator_inputs.metadata, + ) + + stats.successful_runs += 1 + stats.total_scores_created += len(eval_results) + evaluations.extend(eval_results) + + except Exception as e: + # Evaluator failed - log warning and continue with other evaluators + stats.failed_runs += 1 + evaluations_failed += 1 + self._log.warning( + f"Evaluator {evaluator_name} failed on item " + f"{self._get_item_id(item, scope)}: {e}" + ) + + # Create scores for item-level evaluations + item_id = self._get_item_id(item, scope) + for evaluation in evaluations: + self._create_score_for_scope( + scope=scope, + item_id=item_id, + evaluation=evaluation, + additional_metadata=metadata, + ) + scores_created += 1 + + # Run composite evaluator if provided and we have evaluations + if composite_evaluator and evaluations: + try: + composite_evals = await self._run_composite_evaluator( + composite_evaluator, + input=evaluator_inputs.input, + output=evaluator_inputs.output, + expected_output=evaluator_inputs.expected_output, + metadata=evaluator_inputs.metadata, + evaluations=evaluations, + ) + + # Create scores for all composite evaluations + for composite_eval in composite_evals: + self._create_score_for_scope( + scope=scope, + item_id=item_id, + evaluation=composite_eval, + additional_metadata=metadata, + ) + composite_scores_created += 1 + + # Add composite evaluations to the list + evaluations.extend(composite_evals) + + except Exception as e: + self._log.warning(f"Composite evaluator failed on item {item_id}: {e}") + + return ( + scores_created, + composite_scores_created, + evaluations_failed, + evaluations, + ) + + async def _run_evaluator_internal( + self, + evaluator: EvaluatorFunction, + **kwargs: Any, + ) -> List[Evaluation]: + """Run an evaluator function and normalize the result. + + Unlike experiment._run_evaluator, this version raises exceptions + so we can track failures in our statistics. + + Args: + evaluator: The evaluator function to run. + **kwargs: Arguments to pass to the evaluator. + + Returns: + List of Evaluation objects. + + Raises: + Exception: If evaluator raises an exception (not caught). + """ + result = evaluator(**kwargs) + + # Handle async evaluators + if asyncio.iscoroutine(result): + result = await result + + # Normalize to list + if isinstance(result, (dict, Evaluation)): + return [result] # type: ignore + elif isinstance(result, list): + return result + else: + return [] + + async def _run_mapper( + self, + mapper: MapperFunction, + item: Union[TraceWithFullDetails, ObservationsView], + ) -> EvaluatorInputs: + """Run mapper function (handles both sync and async mappers). + + Args: + mapper: The mapper function to run. + item: The API response object to map. + + Returns: + EvaluatorInputs instance. + + Raises: + Exception: If mapper raises an exception. + """ + result = mapper(item=item) + if asyncio.iscoroutine(result): + return await result # type: ignore + return result # type: ignore + + async def _run_composite_evaluator( + self, + composite_evaluator: CompositeEvaluatorFunction, + input: Optional[Any], + output: Optional[Any], + expected_output: Optional[Any], + metadata: Optional[Dict[str, Any]], + evaluations: List[Evaluation], + ) -> List[Evaluation]: + """Run composite evaluator function (handles both sync and async). + + Args: + composite_evaluator: The composite evaluator function. + input: The input data provided to the system. + output: The output generated by the system. + expected_output: The expected/reference output. + metadata: Additional metadata about the evaluation context. + evaluations: List of item-level evaluations. + + Returns: + List of Evaluation objects (normalized from single or list return). + + Raises: + Exception: If composite evaluator raises an exception. + """ + result = composite_evaluator( + input=input, + output=output, + expected_output=expected_output, + metadata=metadata, + evaluations=evaluations, + ) + if asyncio.iscoroutine(result): + result = await result + + # Normalize to list (same as regular evaluator) + if isinstance(result, (dict, Evaluation)): + return [result] # type: ignore + elif isinstance(result, list): + return result + else: + return [] + + def _create_score_for_scope( + self, + scope: str, + item_id: str, + evaluation: Evaluation, + additional_metadata: Optional[Dict[str, Any]], + ) -> None: + """Create a score linked to the appropriate entity based on scope. + + Args: + scope: The type of entity ("traces", "observations"). + item_id: The ID of the entity. + evaluation: The evaluation result to create a score from. + additional_metadata: Additional metadata to merge with evaluation metadata. + """ + # Merge metadata + score_metadata = { + **(evaluation.metadata or {}), + **(additional_metadata or {}), + } + + if scope == "traces": + self.client.create_score( + trace_id=item_id, + name=evaluation.name, + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=score_metadata, + data_type=evaluation.data_type, # type: ignore[arg-type] + config_id=evaluation.config_id, + ) + elif scope == "observations": + self.client.create_score( + observation_id=item_id, + name=evaluation.name, + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=score_metadata, + data_type=evaluation.data_type, # type: ignore[arg-type] + config_id=evaluation.config_id, + ) + + def _build_timestamp_filter( + self, + original_filter: Optional[str], + resume_from: Optional[BatchEvaluationResumeToken], + ) -> Optional[str]: + """Build filter with timestamp constraint for resume capability. + + Args: + original_filter: The original JSON filter string. + resume_from: Optional resume token with timestamp information. + + Returns: + Modified filter string with timestamp constraint, or original filter. + """ + if not resume_from: + return original_filter + + # Parse original filter (should be array) or create empty array + try: + filter_list = json.loads(original_filter) if original_filter else [] + if not isinstance(filter_list, list): + self._log.warning( + f"Filter should be a JSON array, got: {type(filter_list).__name__}" + ) + filter_list = [] + except json.JSONDecodeError: + self._log.warning( + f"Invalid JSON in original filter, ignoring: {original_filter}" + ) + filter_list = [] + + # Add timestamp constraint to filter array + timestamp_field = self._get_timestamp_field_for_scope(resume_from.scope) + timestamp_filter = { + "type": "datetime", + "column": timestamp_field, + "operator": ">", + "value": resume_from.last_processed_timestamp, + } + filter_list.append(timestamp_filter) + + return json.dumps(filter_list) + + @staticmethod + def _get_item_id( + item: Union[TraceWithFullDetails, ObservationsView], + scope: str, + ) -> str: + """Extract ID from item based on scope. + + Args: + item: The API response object. + scope: The type of item. + + Returns: + The item's ID. + """ + return item.id + + @staticmethod + def _get_item_timestamp( + item: Union[TraceWithFullDetails, ObservationsView], + scope: str, + ) -> str: + """Extract timestamp from item based on scope. + + Args: + item: The API response object. + scope: The type of item. + + Returns: + ISO 8601 timestamp string. + """ + if scope == "traces": + # Type narrowing for traces + if hasattr(item, "timestamp"): + return item.timestamp.isoformat() # type: ignore[attr-defined] + elif scope == "observations": + # Type narrowing for observations + if hasattr(item, "start_time"): + return item.start_time.isoformat() # type: ignore[attr-defined] + return "" + + @staticmethod + def _get_timestamp_field_for_scope(scope: str) -> str: + """Get the timestamp field name for filtering based on scope. + + Args: + scope: The type of items. + + Returns: + The field name to use in filters. + """ + if scope == "traces": + return "timestamp" + elif scope == "observations": + return "start_time" + return "timestamp" # Default + + def _build_result( + self, + total_items_fetched: int, + total_items_processed: int, + total_items_failed: int, + total_scores_created: int, + total_composite_scores_created: int, + total_evaluations_failed: int, + evaluator_stats_dict: Dict[str, EvaluatorStats], + resume_token: Optional[BatchEvaluationResumeToken], + completed: bool, + start_time: float, + failed_item_ids: List[str], + error_summary: Dict[str, int], + has_more_items: bool, + item_evaluations: Dict[str, List[Evaluation]], + ) -> BatchEvaluationResult: + """Build the final BatchEvaluationResult. + + Args: + total_items_fetched: Total items fetched. + total_items_processed: Items successfully processed. + total_items_failed: Items that failed. + total_scores_created: Scores from item evaluators. + total_composite_scores_created: Scores from composite evaluator. + total_evaluations_failed: Individual evaluator failures. + evaluator_stats_dict: Per-evaluator statistics. + resume_token: Resume token if incomplete. + completed: Whether evaluation completed fully. + start_time: Start time (unix timestamp). + failed_item_ids: IDs of failed items. + error_summary: Error type counts. + has_more_items: Whether more items exist. + item_evaluations: Dictionary mapping item IDs to their evaluation results. + + Returns: + BatchEvaluationResult instance. + """ + duration = time.time() - start_time + + return BatchEvaluationResult( + total_items_fetched=total_items_fetched, + total_items_processed=total_items_processed, + total_items_failed=total_items_failed, + total_scores_created=total_scores_created, + total_composite_scores_created=total_composite_scores_created, + total_evaluations_failed=total_evaluations_failed, + evaluator_stats=list(evaluator_stats_dict.values()), + resume_token=resume_token, + completed=completed, + duration_seconds=duration, + failed_item_ids=failed_item_ids, + error_summary=error_summary, + has_more_items=has_more_items, + item_evaluations=item_evaluations, + ) diff --git a/tests/test_batch_evaluation.py b/tests/test_batch_evaluation.py new file mode 100644 index 000000000..46bda13d6 --- /dev/null +++ b/tests/test_batch_evaluation.py @@ -0,0 +1,1077 @@ +"""Comprehensive tests for batch evaluation functionality. + +This test suite covers the run_batched_evaluation method which allows evaluating +traces, observations, and sessions fetched from Langfuse with mappers, evaluators, +and composite evaluators. +""" + +import asyncio +import time + +import pytest + +from langfuse import get_client +from langfuse.batch_evaluation import ( + BatchEvaluationResult, + BatchEvaluationResumeToken, + EvaluatorInputs, + EvaluatorStats, +) +from langfuse.experiment import Evaluation +from tests.utils import create_uuid + +# ============================================================================ +# FIXTURES & SETUP +# ============================================================================ + + +pytestmark = pytest.mark.skip(reason="Reason for skipping this file") + + +@pytest.fixture +def langfuse_client(): + """Get a Langfuse client for testing.""" + return get_client() + + +@pytest.fixture +def sample_trace_name(): + """Generate a unique trace name for filtering.""" + return f"batch-eval-test-{create_uuid()}" + + +def simple_trace_mapper(*, item): + """Simple mapper for traces.""" + return EvaluatorInputs( + input=item.input if hasattr(item, "input") else None, + output=item.output if hasattr(item, "output") else None, + expected_output=None, + metadata={"trace_id": item.id}, + ) + + +def simple_evaluator(*, input, output, expected_output=None, metadata=None, **kwargs): + """Simple evaluator that returns a score based on output length.""" + if output is None: + return Evaluation(name="length_score", value=0.0, comment="No output") + + return Evaluation( + name="length_score", + value=float(len(str(output))) / 10.0, + comment=f"Length: {len(str(output))}", + ) + + +# ============================================================================ +# BASIC FUNCTIONALITY TESTS +# ============================================================================ + + +def test_run_batched_evaluation_on_traces_basic(langfuse_client): + """Test basic batch evaluation on traces.""" + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=5, + verbose=True, + ) + + # Validate result structure + assert isinstance(result, BatchEvaluationResult) + assert result.total_items_fetched >= 0 + assert result.total_items_processed >= 0 + assert result.total_scores_created >= 0 + assert result.completed is True + assert isinstance(result.duration_seconds, float) + assert result.duration_seconds > 0 + + # Verify evaluator stats + assert len(result.evaluator_stats) == 1 + stats = result.evaluator_stats[0] + assert isinstance(stats, EvaluatorStats) + assert stats.name == "simple_evaluator" + + +def test_batch_evaluation_with_filter(langfuse_client): + """Test batch evaluation with JSON filter.""" + # Create a trace with specific tag + unique_tag = f"test-filter-{create_uuid()}" + with langfuse_client.start_as_current_span( + name=f"filtered-trace-{create_uuid()}" + ) as span: + span.update_trace( + input="Filtered test", + output="Filtered output", + tags=[unique_tag], + ) + + langfuse_client.flush() + time.sleep(3) + + # Filter format: array of filter conditions + filter_json = f'[{{"type": "arrayOptions", "column": "tags", "operator": "any of", "value": ["{unique_tag}"]}}]' + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + filter=filter_json, + verbose=True, + ) + + # Should only process the filtered trace + assert result.total_items_fetched >= 1 + assert result.completed is True + + +def test_batch_evaluation_with_metadata(langfuse_client): + """Test that additional metadata is added to all scores.""" + + def metadata_checking_evaluator(*, input, output, metadata=None, **kwargs): + return Evaluation( + name="test_score", + value=1.0, + metadata={"evaluator_data": "test"}, + ) + + additional_metadata = { + "batch_run_id": "test-batch-123", + "evaluation_version": "v2.0", + } + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[metadata_checking_evaluator], + metadata=additional_metadata, + max_items=2, + ) + + assert result.total_scores_created > 0 + + # Verify scores were created with merged metadata + langfuse_client.flush() + time.sleep(3) + + # Note: In a real test, you'd verify via API that metadata was merged + # For now, just verify the operation completed + assert result.completed is True + + +def test_result_structure_fields(langfuse_client): + """Test that BatchEvaluationResult has all expected fields.""" + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=3, + ) + + # Check all result fields exist + assert hasattr(result, "total_items_fetched") + assert hasattr(result, "total_items_processed") + assert hasattr(result, "total_items_failed") + assert hasattr(result, "total_scores_created") + assert hasattr(result, "total_composite_scores_created") + assert hasattr(result, "total_evaluations_failed") + assert hasattr(result, "evaluator_stats") + assert hasattr(result, "resume_token") + assert hasattr(result, "completed") + assert hasattr(result, "duration_seconds") + assert hasattr(result, "failed_item_ids") + assert hasattr(result, "error_summary") + assert hasattr(result, "has_more_items") + assert hasattr(result, "item_evaluations") + + # Check types + assert isinstance(result.evaluator_stats, list) + assert isinstance(result.failed_item_ids, list) + assert isinstance(result.error_summary, dict) + assert isinstance(result.completed, bool) + assert isinstance(result.has_more_items, bool) + assert isinstance(result.item_evaluations, dict) + + +# ============================================================================ +# MAPPER FUNCTION TESTS +# ============================================================================ + + +def test_simple_mapper(langfuse_client): + """Test basic mapper functionality.""" + + def custom_mapper(*, item): + return EvaluatorInputs( + input=item.input if hasattr(item, "input") else "no input", + output=item.output if hasattr(item, "output") else "no output", + expected_output=None, + metadata={"custom_field": "test_value"}, + ) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=custom_mapper, + evaluators=[simple_evaluator], + max_items=2, + ) + + assert result.total_items_processed > 0 + + +@pytest.mark.asyncio +async def test_async_mapper(langfuse_client): + """Test that async mappers work correctly.""" + + async def async_mapper(*, item): + await asyncio.sleep(0.01) # Simulate async work + return EvaluatorInputs( + input=item.input if hasattr(item, "input") else None, + output=item.output if hasattr(item, "output") else None, + expected_output=None, + metadata={"async": True}, + ) + + # Note: run_batched_evaluation is synchronous but handles async mappers + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=async_mapper, + evaluators=[simple_evaluator], + max_items=2, + ) + + assert result.total_items_processed > 0 + + +def test_mapper_failure_handling(langfuse_client): + """Test that mapper failures cause items to be skipped.""" + + def failing_mapper(*, item): + raise ValueError("Intentional mapper failure") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=failing_mapper, + evaluators=[simple_evaluator], + max_items=3, + ) + + # All items should fail due to mapper failures + assert result.total_items_failed > 0 + assert len(result.failed_item_ids) > 0 + assert "ValueError" in result.error_summary or "Exception" in result.error_summary + + +def test_mapper_with_missing_fields(langfuse_client): + """Test mapper handles traces with missing fields gracefully.""" + + def robust_mapper(*, item): + # Handle missing fields with defaults + input_val = getattr(item, "input", None) or "default_input" + output_val = getattr(item, "output", None) or "default_output" + + return EvaluatorInputs( + input=input_val, + output=output_val, + expected_output=None, + metadata={}, + ) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=robust_mapper, + evaluators=[simple_evaluator], + max_items=2, + ) + + assert result.total_items_processed > 0 + + +# ============================================================================ +# EVALUATOR TESTS +# ============================================================================ + + +def test_single_evaluator(langfuse_client): + """Test with a single evaluator.""" + + def quality_evaluator(*, input, output, **kwargs): + return Evaluation(name="quality", value=0.85, comment="High quality") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[quality_evaluator], + max_items=2, + ) + + assert result.total_scores_created > 0 + assert len(result.evaluator_stats) == 1 + assert result.evaluator_stats[0].name == "quality_evaluator" + + +def test_multiple_evaluators(langfuse_client): + """Test with multiple evaluators running in parallel.""" + + def accuracy_evaluator(*, input, output, **kwargs): + return Evaluation(name="accuracy", value=0.9) + + def relevance_evaluator(*, input, output, **kwargs): + return Evaluation(name="relevance", value=0.8) + + def safety_evaluator(*, input, output, **kwargs): + return Evaluation(name="safety", value=1.0) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[accuracy_evaluator, relevance_evaluator, safety_evaluator], + max_items=2, + ) + + # Should have 3 evaluators + assert len(result.evaluator_stats) == 3 + assert result.total_scores_created >= result.total_items_processed * 3 + + +@pytest.mark.asyncio +async def test_async_evaluator(langfuse_client): + """Test that async evaluators work correctly.""" + + async def async_evaluator(*, input, output, **kwargs): + await asyncio.sleep(0.01) # Simulate async work + return Evaluation(name="async_score", value=0.75) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[async_evaluator], + max_items=2, + ) + + assert result.total_scores_created > 0 + + +def test_evaluator_returning_list(langfuse_client): + """Test evaluator that returns multiple Evaluations.""" + + def multi_score_evaluator(*, input, output, **kwargs): + return [ + Evaluation(name="score_1", value=0.8), + Evaluation(name="score_2", value=0.9), + Evaluation(name="score_3", value=0.7), + ] + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[multi_score_evaluator], + max_items=2, + ) + + # Should create 3 scores per item + assert result.total_scores_created >= result.total_items_processed * 3 + + +def test_evaluator_failure_statistics(langfuse_client): + """Test that evaluator failures are tracked in statistics.""" + + def working_evaluator(*, input, output, **kwargs): + return Evaluation(name="working", value=1.0) + + def failing_evaluator(*, input, output, **kwargs): + raise RuntimeError("Intentional evaluator failure") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[working_evaluator, failing_evaluator], + max_items=3, + ) + + # Verify evaluator stats + assert len(result.evaluator_stats) == 2 + + working_stats = next( + s for s in result.evaluator_stats if s.name == "working_evaluator" + ) + assert working_stats.successful_runs > 0 + assert working_stats.failed_runs == 0 + + failing_stats = next( + s for s in result.evaluator_stats if s.name == "failing_evaluator" + ) + assert failing_stats.failed_runs > 0 + assert failing_stats.successful_runs == 0 + + # Total evaluations failed should be tracked + assert result.total_evaluations_failed > 0 + + +def test_mixed_sync_async_evaluators(langfuse_client): + """Test mixing synchronous and asynchronous evaluators.""" + + def sync_evaluator(*, input, output, **kwargs): + return Evaluation(name="sync_score", value=0.8) + + async def async_evaluator(*, input, output, **kwargs): + await asyncio.sleep(0.01) + return Evaluation(name="async_score", value=0.9) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[sync_evaluator, async_evaluator], + max_items=2, + ) + + assert len(result.evaluator_stats) == 2 + assert result.total_scores_created >= result.total_items_processed * 2 + + +# ============================================================================ +# COMPOSITE EVALUATOR TESTS +# ============================================================================ + + +def test_composite_evaluator_weighted_average(langfuse_client): + """Test composite evaluator that computes weighted average.""" + + def accuracy_evaluator(*, input, output, **kwargs): + return Evaluation(name="accuracy", value=0.8) + + def relevance_evaluator(*, input, output, **kwargs): + return Evaluation(name="relevance", value=0.9) + + def composite_evaluator(*, input, output, expected_output, metadata, evaluations): + weights = {"accuracy": 0.6, "relevance": 0.4} + total = sum( + e.value * weights.get(e.name, 0) + for e in evaluations + if isinstance(e.value, (int, float)) + ) + + return Evaluation( + name="composite_score", + value=total, + comment=f"Weighted average of {len(evaluations)} metrics", + ) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[accuracy_evaluator, relevance_evaluator], + composite_evaluator=composite_evaluator, + max_items=2, + ) + + # Should have both regular and composite scores + assert result.total_scores_created > 0 + assert result.total_composite_scores_created > 0 + assert result.total_scores_created > result.total_composite_scores_created + + +def test_composite_evaluator_pass_fail(langfuse_client): + """Test composite evaluator that implements pass/fail logic.""" + + def metric1_evaluator(*, input, output, **kwargs): + return Evaluation(name="metric1", value=0.9) + + def metric2_evaluator(*, input, output, **kwargs): + return Evaluation(name="metric2", value=0.7) + + def pass_fail_composite(*, input, output, expected_output, metadata, evaluations): + thresholds = {"metric1": 0.8, "metric2": 0.6} + + passes = all( + e.value >= thresholds.get(e.name, 0) + for e in evaluations + if isinstance(e.value, (int, float)) + ) + + return Evaluation( + name="passes_all_checks", + value=1.0 if passes else 0.0, + comment="All checks passed" if passes else "Some checks failed", + ) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[metric1_evaluator, metric2_evaluator], + composite_evaluator=pass_fail_composite, + max_items=2, + ) + + assert result.total_composite_scores_created > 0 + + +@pytest.mark.asyncio +async def test_async_composite_evaluator(langfuse_client): + """Test async composite evaluator.""" + + def evaluator1(*, input, output, **kwargs): + return Evaluation(name="eval1", value=0.8) + + async def async_composite(*, input, output, expected_output, metadata, evaluations): + await asyncio.sleep(0.01) # Simulate async processing + avg = sum( + e.value for e in evaluations if isinstance(e.value, (int, float)) + ) / len(evaluations) + return Evaluation(name="async_composite", value=avg) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[evaluator1], + composite_evaluator=async_composite, + max_items=2, + ) + + assert result.total_composite_scores_created > 0 + + +def test_composite_evaluator_with_no_evaluations(langfuse_client): + """Test composite evaluator when no evaluations are present.""" + + def always_failing_evaluator(*, input, output, **kwargs): + raise Exception("Always fails") + + def composite_evaluator(*, input, output, expected_output, metadata, evaluations): + # Should not be called if no evaluations succeed + return Evaluation(name="composite", value=0.0) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[always_failing_evaluator], + composite_evaluator=composite_evaluator, + max_items=2, + ) + + # Composite evaluator should not create scores if no evaluations + assert result.total_composite_scores_created == 0 + + +def test_composite_evaluator_failure_handling(langfuse_client): + """Test that composite evaluator failures are handled gracefully.""" + + def evaluator1(*, input, output, **kwargs): + return Evaluation(name="eval1", value=0.8) + + def failing_composite(*, input, output, expected_output, metadata, evaluations): + raise ValueError("Composite evaluator failed") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[evaluator1], + composite_evaluator=failing_composite, + max_items=2, + ) + + # Regular scores should still be created + assert result.total_scores_created > 0 + # But no composite scores + assert result.total_composite_scores_created == 0 + + +# ============================================================================ +# ERROR HANDLING TESTS +# ============================================================================ + + +def test_mapper_failure_skips_item(langfuse_client): + """Test that mapper failure causes item to be skipped.""" + + call_count = {"count": 0} + + def sometimes_failing_mapper(*, item): + call_count["count"] += 1 + if call_count["count"] % 2 == 0: + raise Exception("Mapper failed") + return simple_trace_mapper(item=item) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=sometimes_failing_mapper, + evaluators=[simple_evaluator], + max_items=4, + ) + + # Some items should fail, some should succeed + assert result.total_items_failed > 0 + assert result.total_items_processed > 0 + + +def test_evaluator_failure_continues(langfuse_client): + """Test that one evaluator failing doesn't stop others.""" + + def working_evaluator1(*, input, output, **kwargs): + return Evaluation(name="working1", value=0.8) + + def failing_evaluator(*, input, output, **kwargs): + raise Exception("Evaluator failed") + + def working_evaluator2(*, input, output, **kwargs): + return Evaluation(name="working2", value=0.9) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[working_evaluator1, failing_evaluator, working_evaluator2], + max_items=2, + ) + + # Working evaluators should still create scores + assert result.total_scores_created >= result.total_items_processed * 2 + + # Failing evaluator should be tracked + failing_stats = next( + s for s in result.evaluator_stats if s.name == "failing_evaluator" + ) + assert failing_stats.failed_runs > 0 + + +def test_all_evaluators_fail(langfuse_client): + """Test when all evaluators fail but item is still processed.""" + + def failing_evaluator1(*, input, output, **kwargs): + raise Exception("Failed 1") + + def failing_evaluator2(*, input, output, **kwargs): + raise Exception("Failed 2") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[failing_evaluator1, failing_evaluator2], + max_items=2, + ) + + # Items should be processed even if all evaluators fail + assert result.total_items_processed > 0 + # But no scores created + assert result.total_scores_created == 0 + # All evaluations failed + assert result.total_evaluations_failed > 0 + + +# ============================================================================ +# EDGE CASES TESTS +# ============================================================================ + + +def test_empty_results_handling(langfuse_client): + """Test batch evaluation when filter returns no items.""" + nonexistent_name = f"nonexistent-trace-{create_uuid()}" + nonexistent_filter = f'[{{"type": "string", "column": "name", "operator": "=", "value": "{nonexistent_name}"}}]' + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + filter=nonexistent_filter, + ) + + assert result.total_items_fetched == 0 + assert result.total_items_processed == 0 + assert result.total_scores_created == 0 + assert result.completed is True + assert result.has_more_items is False + + +def test_max_items_zero(langfuse_client): + """Test with max_items=0 (should process no items).""" + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=0, + ) + + assert result.total_items_fetched == 0 + assert result.total_items_processed == 0 + + +def test_evaluation_value_type_conversions(langfuse_client): + """Test that different evaluation value types are handled correctly.""" + + def multi_type_evaluator(*, input, output, **kwargs): + return [ + Evaluation(name="int_score", value=5), # int + Evaluation(name="float_score", value=0.85), # float + Evaluation(name="bool_score", value=True), # bool + Evaluation(name="none_score", value=None), # None + ] + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[multi_type_evaluator], + max_items=1, + ) + + # All value types should be converted and scores created + assert result.total_scores_created >= 4 + + +# ============================================================================ +# PAGINATION TESTS +# ============================================================================ + + +def test_pagination_with_max_items(langfuse_client): + """Test that max_items limit is respected.""" + # Create more traces to ensure we have enough data + for i in range(10): + with langfuse_client.start_as_current_span( + name=f"pagination-test-{create_uuid()}" + ) as span: + span.update_trace( + input=f"Input {i}", + output=f"Output {i}", + tags=["pagination_test"], + ) + + langfuse_client.flush() + time.sleep(3) + + filter_json = '[{"type": "arrayOptions", "column": "tags", "operator": "any of", "value": ["pagination_test"]}]' + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + filter=filter_json, + max_items=5, + fetch_batch_size=2, + ) + + # Should not exceed max_items + assert result.total_items_processed <= 5 + + +def test_has_more_items_flag(langfuse_client): + """Test that has_more_items flag is set correctly when max_items is reached.""" + # Create enough traces to exceed max_items + batch_tag = f"batch-test-{create_uuid()}" + for i in range(15): + with langfuse_client.start_as_current_span(name=f"more-items-test-{i}") as span: + span.update_trace( + input=f"Input {i}", + output=f"Output {i}", + tags=[batch_tag], + ) + + langfuse_client.flush() + time.sleep(3) + + filter_json = f'[{{"type": "arrayOptions", "column": "tags", "operator": "any of", "value": ["{batch_tag}"]}}]' + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + filter=filter_json, + max_items=5, + fetch_batch_size=2, + ) + + # has_more_items should be True if we hit the limit + if result.total_items_fetched >= 5: + assert result.has_more_items is True + + +def test_fetch_batch_size_parameter(langfuse_client): + """Test that different fetch_batch_size values work correctly.""" + for batch_size in [1, 5, 10]: + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=3, + fetch_batch_size=batch_size, + ) + + # Should complete regardless of batch size + assert result.completed is True or result.total_items_processed > 0 + + +# ============================================================================ +# RESUME FUNCTIONALITY TESTS +# ============================================================================ + + +def test_resume_token_structure(langfuse_client): + """Test that BatchEvaluationResumeToken has correct structure.""" + resume_token = BatchEvaluationResumeToken( + scope="traces", + filter='{"test": "filter"}', + last_processed_timestamp="2024-01-01T00:00:00Z", + last_processed_id="trace-123", + items_processed=10, + ) + + assert resume_token.scope == "traces" + assert resume_token.filter == '{"test": "filter"}' + assert resume_token.last_processed_timestamp == "2024-01-01T00:00:00Z" + assert resume_token.last_processed_id == "trace-123" + assert resume_token.items_processed == 10 + + +# ============================================================================ +# CONCURRENCY TESTS +# ============================================================================ + + +def test_max_concurrency_parameter(langfuse_client): + """Test that max_concurrency parameter works correctly.""" + for concurrency in [1, 5, 10]: + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=3, + max_concurrency=concurrency, + ) + + # Should complete regardless of concurrency + assert result.completed is True or result.total_items_processed > 0 + + +# ============================================================================ +# STATISTICS TESTS +# ============================================================================ + + +def test_evaluator_stats_structure(langfuse_client): + """Test that EvaluatorStats has correct structure.""" + + def test_evaluator(*, input, output, **kwargs): + return Evaluation(name="test", value=1.0) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[test_evaluator], + max_items=2, + ) + + assert len(result.evaluator_stats) == 1 + stats = result.evaluator_stats[0] + + # Check all fields exist + assert hasattr(stats, "name") + assert hasattr(stats, "total_runs") + assert hasattr(stats, "successful_runs") + assert hasattr(stats, "failed_runs") + assert hasattr(stats, "total_scores_created") + + # Check values + assert stats.name == "test_evaluator" + assert stats.total_runs == result.total_items_processed + assert stats.successful_runs == result.total_items_processed + assert stats.failed_runs == 0 + + +def test_evaluator_stats_tracking(langfuse_client): + """Test that evaluator statistics are tracked correctly.""" + + call_count = {"count": 0} + + def sometimes_failing_evaluator(*, input, output, **kwargs): + call_count["count"] += 1 + if call_count["count"] % 2 == 0: + raise Exception("Failed") + return Evaluation(name="test", value=1.0) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[sometimes_failing_evaluator], + max_items=4, + ) + + stats = result.evaluator_stats[0] + assert stats.total_runs == result.total_items_processed + assert stats.successful_runs > 0 + assert stats.failed_runs > 0 + assert stats.successful_runs + stats.failed_runs == stats.total_runs + + +def test_error_summary_aggregation(langfuse_client): + """Test that error types are aggregated correctly in error_summary.""" + + def failing_mapper(*, item): + raise ValueError("Mapper error") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=failing_mapper, + evaluators=[simple_evaluator], + max_items=3, + ) + + # Error summary should contain the error type + assert len(result.error_summary) > 0 + assert any("Error" in key for key in result.error_summary.keys()) + + +def test_failed_item_ids_collected(langfuse_client): + """Test that failed item IDs are collected.""" + + def failing_mapper(*, item): + raise Exception("Failed") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=failing_mapper, + evaluators=[simple_evaluator], + max_items=3, + ) + + assert len(result.failed_item_ids) > 0 + # Each failed ID should be a string + assert all(isinstance(item_id, str) for item_id in result.failed_item_ids) + + +# ============================================================================ +# PERFORMANCE TESTS +# ============================================================================ + + +def test_duration_tracking(langfuse_client): + """Test that duration is tracked correctly.""" + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=2, + ) + + assert result.duration_seconds > 0 + assert result.duration_seconds < 60 # Should complete quickly for small batch + + +def test_verbose_logging(langfuse_client): + """Test that verbose=True doesn't cause errors.""" + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=2, + verbose=True, # Should log progress + ) + + assert result.completed is True + + +# ============================================================================ +# ITEM EVALUATIONS TESTS +# ============================================================================ + + +def test_item_evaluations_basic(langfuse_client): + """Test that item_evaluations dict contains correct structure.""" + + def test_evaluator(*, input, output, **kwargs): + return Evaluation(name="test_metric", value=0.5) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[test_evaluator], + max_items=3, + ) + + # Check that item_evaluations is a dict + assert isinstance(result.item_evaluations, dict) + + # Should have evaluations for each processed item + assert len(result.item_evaluations) == result.total_items_processed + + # Each entry should be a list of Evaluation objects + for item_id, evaluations in result.item_evaluations.items(): + assert isinstance(item_id, str) + assert isinstance(evaluations, list) + assert all(isinstance(e, Evaluation) for e in evaluations) + # Should have one evaluation per evaluator + assert len(evaluations) == 1 + assert evaluations[0].name == "test_metric" + + +def test_item_evaluations_multiple_evaluators(langfuse_client): + """Test item_evaluations with multiple evaluators.""" + + def accuracy_evaluator(*, input, output, **kwargs): + return Evaluation(name="accuracy", value=0.8) + + def relevance_evaluator(*, input, output, **kwargs): + return Evaluation(name="relevance", value=0.9) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[accuracy_evaluator, relevance_evaluator], + max_items=2, + ) + + # Check structure + assert len(result.item_evaluations) == result.total_items_processed + + # Each item should have evaluations from both evaluators + for item_id, evaluations in result.item_evaluations.items(): + assert len(evaluations) == 2 + eval_names = {e.name for e in evaluations} + assert eval_names == {"accuracy", "relevance"} + + +def test_item_evaluations_with_composite(langfuse_client): + """Test that item_evaluations includes composite evaluations.""" + + def base_evaluator(*, input, output, **kwargs): + return Evaluation(name="base_score", value=0.7) + + def composite_evaluator(*, input, output, expected_output, metadata, evaluations): + return Evaluation( + name="composite_score", + value=sum( + e.value for e in evaluations if isinstance(e.value, (int, float)) + ), + ) + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=simple_trace_mapper, + evaluators=[base_evaluator], + composite_evaluator=composite_evaluator, + max_items=2, + ) + + # Each item should have both base and composite evaluations + for item_id, evaluations in result.item_evaluations.items(): + assert len(evaluations) == 2 + eval_names = {e.name for e in evaluations} + assert eval_names == {"base_score", "composite_score"} + + # Verify composite scores were created + assert result.total_composite_scores_created > 0 + + +def test_item_evaluations_empty_on_failure(langfuse_client): + """Test that failed items don't appear in item_evaluations.""" + + def failing_mapper(*, item): + raise Exception("Mapper failed") + + result = langfuse_client.run_batched_evaluation( + scope="traces", + mapper=failing_mapper, + evaluators=[simple_evaluator], + max_items=3, + ) + + # All items failed, so item_evaluations should be empty + assert len(result.item_evaluations) == 0 + assert result.total_items_failed > 0 diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 81a874ae4..39c8de92d 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1959,9 +1959,9 @@ def test_start_as_current_observation_types(): expected_types = {obs_type.upper() for obs_type in observation_types} | { "SPAN" } # includes parent span - assert expected_types.issubset( - found_types - ), f"Missing types: {expected_types - found_types}" + assert expected_types.issubset(found_types), ( + f"Missing types: {expected_types - found_types}" + ) # Verify each specific observation exists for obs_type in observation_types: @@ -2005,25 +2005,25 @@ def test_that_generation_like_properties_are_actually_created(): ) as obs: # Verify the properties are accessible on the observation object if hasattr(obs, "model"): - assert ( - obs.model == test_model - ), f"{obs_type} should have model property" + assert obs.model == test_model, ( + f"{obs_type} should have model property" + ) if hasattr(obs, "completion_start_time"): - assert ( - obs.completion_start_time == test_completion_start_time - ), f"{obs_type} should have completion_start_time property" + assert obs.completion_start_time == test_completion_start_time, ( + f"{obs_type} should have completion_start_time property" + ) if hasattr(obs, "model_parameters"): - assert ( - obs.model_parameters == test_model_parameters - ), f"{obs_type} should have model_parameters property" + assert obs.model_parameters == test_model_parameters, ( + f"{obs_type} should have model_parameters property" + ) if hasattr(obs, "usage_details"): - assert ( - obs.usage_details == test_usage_details - ), f"{obs_type} should have usage_details property" + assert obs.usage_details == test_usage_details, ( + f"{obs_type} should have usage_details property" + ) if hasattr(obs, "cost_details"): - assert ( - obs.cost_details == test_cost_details - ), f"{obs_type} should have cost_details property" + assert obs.cost_details == test_cost_details, ( + f"{obs_type} should have cost_details property" + ) langfuse.flush() @@ -2037,28 +2037,28 @@ def test_that_generation_like_properties_are_actually_created(): for obs in trace.observations if obs.name == f"test-{obs_type}" and obs.type == obs_type.upper() ] - assert ( - len(observations) == 1 - ), f"Expected one {obs_type.upper()} observation, but found {len(observations)}" + assert len(observations) == 1, ( + f"Expected one {obs_type.upper()} observation, but found {len(observations)}" + ) obs = observations[0] assert obs.model == test_model, f"{obs_type} should have model property" - assert ( - obs.model_parameters == test_model_parameters - ), f"{obs_type} should have model_parameters property" + assert obs.model_parameters == test_model_parameters, ( + f"{obs_type} should have model_parameters property" + ) # usage_details assert hasattr(obs, "usage_details"), f"{obs_type} should have usage_details" - assert obs.usage_details == dict( - test_usage_details, total=30 - ), f"{obs_type} should persist usage_details" # API adds total + assert obs.usage_details == dict(test_usage_details, total=30), ( + f"{obs_type} should persist usage_details" + ) # API adds total - assert ( - obs.cost_details == test_cost_details - ), f"{obs_type} should persist cost_details" + assert obs.cost_details == test_cost_details, ( + f"{obs_type} should persist cost_details" + ) # completion_start_time, because of time skew not asserting time - assert ( - obs.completion_start_time is not None - ), f"{obs_type} should persist completion_start_time property" + assert obs.completion_start_time is not None, ( + f"{obs_type} should persist completion_start_time property" + ) diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 0eac5c617..0c82c1a6f 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1728,12 +1728,12 @@ def root_function(): # Verify results assert items == ["item_0", "item_1", "item_2"] - assert ( - span_info["generator_span_id"] != "0000000000000000" - ), "Generator context should be preserved" - assert ( - span_info["root_span_id"] != span_info["generator_span_id"] - ), "Should have different span IDs" + assert span_info["generator_span_id"] != "0000000000000000", ( + "Generator context should be preserved" + ) + assert span_info["root_span_id"] != span_info["generator_span_id"], ( + "Should have different span IDs" + ) # Verify trace structure trace_data = get_api().trace.get(mock_trace_id) @@ -1794,12 +1794,12 @@ async def root_function(): # Verify results assert items == ["async_item_0", "async_item_1", "async_item_2"] - assert ( - span_info["generator_span_id"] != "0000000000000000" - ), "Generator context should be preserved" - assert ( - span_info["root_span_id"] != span_info["generator_span_id"] - ), "Should have different span IDs" + assert span_info["generator_span_id"] != "0000000000000000", ( + "Generator context should be preserved" + ) + assert span_info["root_span_id"] != span_info["generator_span_id"], ( + "Should have different span IDs" + ) # Verify trace structure trace_data = get_api().trace.get(mock_trace_id) @@ -1860,15 +1860,15 @@ async def parent_function(): assert items == ["child_0", "child_1"] # Verify span hierarchy - assert ( - span_info["parent_span_id"] != span_info["child_span_id"] - ), "Parent and child should have different span IDs" - assert ( - span_info["parent_trace_id"] == span_info["child_trace_id"] - ), "Parent and child should share same trace ID" - assert ( - span_info["child_span_id"] != "0000000000000000" - ), "Child context should be preserved" + assert span_info["parent_span_id"] != span_info["child_span_id"], ( + "Parent and child should have different span IDs" + ) + assert span_info["parent_trace_id"] == span_info["child_trace_id"], ( + "Parent and child should share same trace ID" + ) + assert span_info["child_span_id"] != "0000000000000000", ( + "Child context should be preserved" + ) # Verify trace structure trace_data = get_api().trace.get(mock_trace_id) diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py index bcb2626b9..edda545fd 100644 --- a/tests/test_deprecation.py +++ b/tests/test_deprecation.py @@ -109,12 +109,12 @@ def test_deprecated_function_warnings(self, langfuse_client, func_info): deprecation_warnings = [ w for w in warning_list if issubclass(w.category, DeprecationWarning) ] - assert ( - len(deprecation_warnings) > 0 - ), f"No DeprecationWarning emitted for {target}.{method_name}" + assert len(deprecation_warnings) > 0, ( + f"No DeprecationWarning emitted for {target}.{method_name}" + ) # Check that the warning message matches expected warning_messages = [str(w.message) for w in deprecation_warnings] - assert ( - expected_message in warning_messages - ), f"Expected warning message not found for {target}.{method_name}. Got: {warning_messages}" + assert expected_message in warning_messages, ( + f"Expected warning message not found for {target}.{method_name}. Got: {warning_messages}" + ) diff --git a/tests/test_experiments.py b/tests/test_experiments.py index 71f2e5926..3ba8b4afa 100644 --- a/tests/test_experiments.py +++ b/tests/test_experiments.py @@ -106,33 +106,33 @@ def test_run_experiment_on_local_dataset(sample_dataset): assert trace is not None, f"Trace {trace_id} should exist" # Validate trace name - assert ( - trace.name == "experiment-item-run" - ), f"Trace {trace_id} should have correct name" + assert trace.name == "experiment-item-run", ( + f"Trace {trace_id} should have correct name" + ) # Validate trace input - should contain the experiment item assert trace.input is not None, f"Trace {trace_id} should have input" expected_input = expected_inputs[i] # The input should contain the item data in some form - assert expected_input in str( - trace.input - ), f"Trace {trace_id} input should contain '{expected_input}'" + assert expected_input in str(trace.input), ( + f"Trace {trace_id} input should contain '{expected_input}'" + ) # Validate trace output - should be the task result assert trace.output is not None, f"Trace {trace_id} should have output" expected_output = expected_outputs[i] - assert ( - trace.output == expected_output - ), f"Trace {trace_id} output should be '{expected_output}', got '{trace.output}'" + assert trace.output == expected_output, ( + f"Trace {trace_id} output should be '{expected_output}', got '{trace.output}'" + ) # Validate trace metadata contains experiment name assert trace.metadata is not None, f"Trace {trace_id} should have metadata" - assert ( - "experiment_name" in trace.metadata - ), f"Trace {trace_id} metadata should contain experiment_name" - assert ( - trace.metadata["experiment_name"] == "Euro capitals" - ), f"Trace {trace_id} metadata should have correct experiment_name" + assert "experiment_name" in trace.metadata, ( + f"Trace {trace_id} metadata should contain experiment_name" + ) + assert trace.metadata["experiment_name"] == "Euro capitals", ( + f"Trace {trace_id} metadata should have correct experiment_name" + ) def test_run_experiment_on_langfuse_dataset(): @@ -199,9 +199,9 @@ def test_run_experiment_on_langfuse_dataset(): assert trace is not None, f"Trace {trace_id} should exist" # Validate trace name - assert ( - trace.name == "experiment-item-run" - ), f"Trace {trace_id} should have correct name" + assert trace.name == "experiment-item-run", ( + f"Trace {trace_id} should have correct name" + ) # Validate trace input and output match expected pairs assert trace.input is not None, f"Trace {trace_id} should have input" @@ -214,54 +214,54 @@ def test_run_experiment_on_langfuse_dataset(): matching_input = expected_input break - assert ( - matching_input is not None - ), f"Trace {trace_id} input '{trace_input_str}' should contain one of {list(expected_data.keys())}" + assert matching_input is not None, ( + f"Trace {trace_id} input '{trace_input_str}' should contain one of {list(expected_data.keys())}" + ) # Validate trace output matches the expected output for this input assert trace.output is not None, f"Trace {trace_id} should have output" expected_output = expected_data[matching_input] - assert ( - trace.output == expected_output - ), f"Trace {trace_id} output should be '{expected_output}', got '{trace.output}'" + assert trace.output == expected_output, ( + f"Trace {trace_id} output should be '{expected_output}', got '{trace.output}'" + ) # Validate trace metadata contains experiment and dataset info assert trace.metadata is not None, f"Trace {trace_id} should have metadata" - assert ( - "experiment_name" in trace.metadata - ), f"Trace {trace_id} metadata should contain experiment_name" - assert ( - trace.metadata["experiment_name"] == experiment_name - ), f"Trace {trace_id} metadata should have correct experiment_name" + assert "experiment_name" in trace.metadata, ( + f"Trace {trace_id} metadata should contain experiment_name" + ) + assert trace.metadata["experiment_name"] == experiment_name, ( + f"Trace {trace_id} metadata should have correct experiment_name" + ) # Validate dataset-specific metadata fields - assert ( - "dataset_id" in trace.metadata - ), f"Trace {trace_id} metadata should contain dataset_id" - assert ( - trace.metadata["dataset_id"] == dataset.id - ), f"Trace {trace_id} metadata should have correct dataset_id" + assert "dataset_id" in trace.metadata, ( + f"Trace {trace_id} metadata should contain dataset_id" + ) + assert trace.metadata["dataset_id"] == dataset.id, ( + f"Trace {trace_id} metadata should have correct dataset_id" + ) - assert ( - "dataset_item_id" in trace.metadata - ), f"Trace {trace_id} metadata should contain dataset_item_id" + assert "dataset_item_id" in trace.metadata, ( + f"Trace {trace_id} metadata should contain dataset_item_id" + ) # Get the dataset item ID from metadata and validate it exists dataset_item_id = trace.metadata["dataset_item_id"] - assert ( - dataset_item_id in dataset_item_map - ), f"Trace {trace_id} metadata dataset_item_id should correspond to a valid dataset item" + assert dataset_item_id in dataset_item_map, ( + f"Trace {trace_id} metadata dataset_item_id should correspond to a valid dataset item" + ) # Validate the dataset item input matches the trace input dataset_item = dataset_item_map[dataset_item_id] - assert ( - dataset_item.input == matching_input - ), f"Trace {trace_id} should correspond to dataset item with input '{matching_input}'" + assert dataset_item.input == matching_input, ( + f"Trace {trace_id} should correspond to dataset item with input '{matching_input}'" + ) assert dataset_run is not None, f"Dataset run {dataset_run_id} should exist" assert dataset_run.name == result.run_name, "Dataset run should have correct name" - assert ( - dataset_run.description == "Test on Langfuse dataset" - ), "Dataset run should have correct description" + assert dataset_run.description == "Test on Langfuse dataset", ( + "Dataset run should have correct description" + ) # Get dataset run items to verify trace linkage dataset_run_items = api.dataset_run_items.list( @@ -797,3 +797,59 @@ def mock_task_with_boolean_results(*, item: ExperimentItem, **kwargs): for score in trace.scores: assert score.data_type == "BOOLEAN" + + +def test_experiment_composite_evaluator_weighted_average(): + """Test composite evaluator in experiments that computes weighted average.""" + langfuse_client = get_client() + + def accuracy_evaluator(*, input, output, **kwargs): + return Evaluation(name="accuracy", value=0.8) + + def relevance_evaluator(*, input, output, **kwargs): + return Evaluation(name="relevance", value=0.9) + + def composite_evaluator(*, input, output, expected_output, metadata, evaluations): + weights = {"accuracy": 0.6, "relevance": 0.4} + total = sum( + e.value * weights.get(e.name, 0) + for e in evaluations + if isinstance(e.value, (int, float)) + ) + + return Evaluation( + name="composite_score", + value=total, + comment=f"Weighted average of {len(evaluations)} metrics", + ) + + data = [ + {"input": "Test 1", "expected_output": "Output 1"}, + {"input": "Test 2", "expected_output": "Output 2"}, + ] + + result = langfuse_client.run_experiment( + name=f"Composite Test {create_uuid()}", + data=data, + task=mock_task, + evaluators=[accuracy_evaluator, relevance_evaluator], + composite_evaluator=composite_evaluator, + ) + + # Verify results + assert len(result.item_results) == 2 + + for item_result in result.item_results: + # Should have 3 evaluations: accuracy, relevance, and composite_score + assert len(item_result.evaluations) == 3 + eval_names = [e.name for e in item_result.evaluations] + assert "accuracy" in eval_names + assert "relevance" in eval_names + assert "composite_score" in eval_names + + # Check composite score value + composite_eval = next( + e for e in item_result.evaluations if e.name == "composite_score" + ) + expected_value = 0.8 * 0.6 + 0.9 * 0.4 # 0.84 + assert abs(composite_eval.value - expected_value) < 0.001 diff --git a/tests/test_langchain.py b/tests/test_langchain.py index b4cf828b2..14c25446f 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -814,9 +814,9 @@ def _generate_random_dict(n: int, key_length: int = 8) -> Dict[str, Any]: overhead = duration_with_langfuse - duration_without_langfuse print(f"Langfuse overhead: {overhead}ms") - assert ( - overhead < 100 - ), f"Langfuse tracing overhead of {overhead}ms exceeds threshold" + assert overhead < 100, ( + f"Langfuse tracing overhead of {overhead}ms exceeds threshold" + ) langfuse.flush() diff --git a/tests/test_otel.py b/tests/test_otel.py index ca87691db..89c028c68 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -207,14 +207,14 @@ def verify_span_attribute( ): """Verify that a span has a specific attribute with an optional expected value.""" attributes = span_data["attributes"] - assert ( - attribute_key in attributes - ), f"Attribute {attribute_key} not found in span" + assert attribute_key in attributes, ( + f"Attribute {attribute_key} not found in span" + ) if expected_value is not None: - assert ( - attributes[attribute_key] == expected_value - ), f"Expected {attribute_key} to be {expected_value}, got {attributes[attribute_key]}" + assert attributes[attribute_key] == expected_value, ( + f"Expected {attribute_key} to be {expected_value}, got {attributes[attribute_key]}" + ) return attributes[attribute_key] @@ -226,20 +226,20 @@ def verify_json_attribute( parsed_json = json.loads(json_string) if expected_dict is not None: - assert ( - parsed_json == expected_dict - ), f"Expected JSON {attribute_key} to be {expected_dict}, got {parsed_json}" + assert parsed_json == expected_dict, ( + f"Expected JSON {attribute_key} to be {expected_dict}, got {parsed_json}" + ) return parsed_json def assert_parent_child_relationship(self, parent_span: dict, child_span: dict): """Verify parent-child relationship between two spans.""" - assert ( - child_span["parent_span_id"] == parent_span["span_id"] - ), f"Child span {child_span['name']} should have parent {parent_span['name']}" - assert ( - child_span["trace_id"] == parent_span["trace_id"] - ), f"Child span {child_span['name']} should have same trace ID as parent {parent_span['name']}" + assert child_span["parent_span_id"] == parent_span["span_id"], ( + f"Child span {child_span['name']} should have parent {parent_span['name']}" + ) + assert child_span["trace_id"] == parent_span["trace_id"], ( + f"Child span {child_span['name']} should have same trace ID as parent {parent_span['name']}" + ) class TestBasicSpans(TestOTelBase): @@ -255,9 +255,9 @@ def test_basic_span_creation(self, langfuse_client, memory_exporter): spans = self.get_spans_by_name(memory_exporter, "test-span") # Verify we created exactly one span - assert ( - len(spans) == 1 - ), f"Expected 1 span named 'test-span', but found {len(spans)}" + assert len(spans) == 1, ( + f"Expected 1 span named 'test-span', but found {len(spans)}" + ) span_data = spans[0] # Verify the span attributes @@ -617,9 +617,9 @@ def test_start_as_current_observation_types(self, langfuse_client, memory_export for obs_type in observation_types: expected_name = f"test-{obs_type}" matching_spans = [span for span in spans if span["name"] == expected_name] - assert ( - len(matching_spans) == 1 - ), f"Expected one span with name {expected_name}" + assert len(matching_spans) == 1, ( + f"Expected one span with name {expected_name}" + ) span_data = matching_spans[0] expected_otel_type = obs_type # OTEL attributes use lowercase @@ -627,9 +627,9 @@ def test_start_as_current_observation_types(self, langfuse_client, memory_export LangfuseOtelSpanAttributes.OBSERVATION_TYPE ) - assert ( - actual_type == expected_otel_type - ), f"Expected observation type {expected_otel_type}, got {actual_type}" + assert actual_type == expected_otel_type, ( + f"Expected observation type {expected_otel_type}, got {actual_type}" + ) def test_start_observation(self, langfuse_client, memory_exporter): """Test creating different observation types using start_observation.""" @@ -690,81 +690,81 @@ def test_start_observation(self, langfuse_client, memory_exporter): for obs_type in observation_types: expected_name = f"factory-{obs_type}" matching_spans = [span for span in spans if span["name"] == expected_name] - assert ( - len(matching_spans) == 1 - ), f"Expected one span with name {expected_name}, found {len(matching_spans)}" + assert len(matching_spans) == 1, ( + f"Expected one span with name {expected_name}, found {len(matching_spans)}" + ) span_data = matching_spans[0] actual_type = span_data["attributes"].get( LangfuseOtelSpanAttributes.OBSERVATION_TYPE ) - assert ( - actual_type == obs_type - ), f"Factory pattern failed: Expected observation type {obs_type}, got {actual_type}" + assert actual_type == obs_type, ( + f"Factory pattern failed: Expected observation type {obs_type}, got {actual_type}" + ) # Ensure returned objects are of correct types for obs_type, obs_instance in created_observations: if obs_type == "span": from langfuse._client.span import LangfuseSpan - assert isinstance( - obs_instance, LangfuseSpan - ), f"Expected LangfuseSpan, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseSpan), ( + f"Expected LangfuseSpan, got {type(obs_instance)}" + ) elif obs_type == "generation": from langfuse._client.span import LangfuseGeneration - assert isinstance( - obs_instance, LangfuseGeneration - ), f"Expected LangfuseGeneration, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseGeneration), ( + f"Expected LangfuseGeneration, got {type(obs_instance)}" + ) elif obs_type == "agent": from langfuse._client.span import LangfuseAgent - assert isinstance( - obs_instance, LangfuseAgent - ), f"Expected LangfuseAgent, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseAgent), ( + f"Expected LangfuseAgent, got {type(obs_instance)}" + ) elif obs_type == "tool": from langfuse._client.span import LangfuseTool - assert isinstance( - obs_instance, LangfuseTool - ), f"Expected LangfuseTool, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseTool), ( + f"Expected LangfuseTool, got {type(obs_instance)}" + ) elif obs_type == "chain": from langfuse._client.span import LangfuseChain - assert isinstance( - obs_instance, LangfuseChain - ), f"Expected LangfuseChain, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseChain), ( + f"Expected LangfuseChain, got {type(obs_instance)}" + ) elif obs_type == "retriever": from langfuse._client.span import LangfuseRetriever - assert isinstance( - obs_instance, LangfuseRetriever - ), f"Expected LangfuseRetriever, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseRetriever), ( + f"Expected LangfuseRetriever, got {type(obs_instance)}" + ) elif obs_type == "evaluator": from langfuse._client.span import LangfuseEvaluator - assert isinstance( - obs_instance, LangfuseEvaluator - ), f"Expected LangfuseEvaluator, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseEvaluator), ( + f"Expected LangfuseEvaluator, got {type(obs_instance)}" + ) elif obs_type == "embedding": from langfuse._client.span import LangfuseEmbedding - assert isinstance( - obs_instance, LangfuseEmbedding - ), f"Expected LangfuseEmbedding, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseEmbedding), ( + f"Expected LangfuseEmbedding, got {type(obs_instance)}" + ) elif obs_type == "guardrail": from langfuse._client.span import LangfuseGuardrail - assert isinstance( - obs_instance, LangfuseGuardrail - ), f"Expected LangfuseGuardrail, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseGuardrail), ( + f"Expected LangfuseGuardrail, got {type(obs_instance)}" + ) elif obs_type == "event": from langfuse._client.span import LangfuseEvent - assert isinstance( - obs_instance, LangfuseEvent - ), f"Expected LangfuseEvent, got {type(obs_instance)}" + assert isinstance(obs_instance, LangfuseEvent), ( + f"Expected LangfuseEvent, got {type(obs_instance)}" + ) def test_custom_trace_id(self, langfuse_client, memory_exporter): """Test setting a custom trace ID.""" @@ -785,9 +785,9 @@ def test_custom_trace_id(self, langfuse_client, memory_exporter): assert len(spans) == 1, "Expected one span" span_data = spans[0] - assert ( - span_data["trace_id"] == custom_trace_id - ), "Trace ID doesn't match custom ID" + assert span_data["trace_id"] == custom_trace_id, ( + "Trace ID doesn't match custom ID" + ) assert span_data["attributes"][LangfuseOtelSpanAttributes.AS_ROOT] is True # Test additional spans with the same trace context @@ -799,9 +799,9 @@ def test_custom_trace_id(self, langfuse_client, memory_exporter): # Verify child span uses the same trace ID child_spans = self.get_spans_by_name(memory_exporter, "child-span") assert len(child_spans) == 1, "Expected one child span" - assert ( - child_spans[0]["trace_id"] == custom_trace_id - ), "Child span has wrong trace ID" + assert child_spans[0]["trace_id"] == custom_trace_id, ( + "Child span has wrong trace ID" + ) def test_custom_parent_span_id(self, langfuse_client, memory_exporter): """Test setting a custom parent span ID.""" @@ -1115,9 +1115,9 @@ def test_non_error_levels_dont_set_otel_status( from opentelemetry.trace.status import StatusCode # Default status should be UNSET, not ERROR - assert ( - raw_span.status.status_code != StatusCode.ERROR - ), f"Level {level} should not set ERROR status" + assert raw_span.status.status_code != StatusCode.ERROR, ( + f"Level {level} should not set ERROR status" + ) def test_multiple_error_updates(self, langfuse_client, memory_exporter): """Test that multiple ERROR level updates work correctly.""" @@ -1206,12 +1206,12 @@ def test_different_observation_types_error_handling( raw_span = obs_spans[0] from opentelemetry.trace.status import StatusCode - assert ( - raw_span.status.status_code == StatusCode.ERROR - ), f"{obs_type} should have ERROR status" - assert ( - raw_span.status.description == f"{obs_type} failed" - ), f"{obs_type} should have correct description" + assert raw_span.status.status_code == StatusCode.ERROR, ( + f"{obs_type} should have ERROR status" + ) + assert raw_span.status.description == f"{obs_type} failed", ( + f"{obs_type} should have correct description" + ) class TestAdvancedSpans(TestOTelBase): @@ -1387,9 +1387,9 @@ def test_sampling(self, monkeypatch, tracer_provider, mock_processor_init): span.end() # With a sample rate of 0, we should have no spans - assert ( - len(sampled_exporter.get_finished_spans()) == 0 - ), "Expected no spans with 0 sampling" + assert len(sampled_exporter.get_finished_spans()) == 0, ( + "Expected no spans with 0 sampling" + ) # Restore the original provider trace_api.set_tracer_provider(original_provider) @@ -1445,9 +1445,9 @@ def test_disabled_tracing(self, monkeypatch, tracer_provider, mock_processor_ini # Verify no spans were created spans = exporter.get_finished_spans() - assert ( - len(spans) == 0 - ), f"Expected no spans when tracing is disabled, got {len(spans)}" + assert len(spans) == 0, ( + f"Expected no spans when tracing is disabled, got {len(spans)}" + ) def test_trace_id_generation(self, langfuse_client): """Test trace ID generation follows expected format.""" @@ -1456,12 +1456,12 @@ def test_trace_id_generation(self, langfuse_client): trace_id2 = langfuse_client.create_trace_id() # Verify format: 32 hex characters - assert ( - len(trace_id1) == 32 - ), f"Trace ID length should be 32, got {len(trace_id1)}" - assert ( - len(trace_id2) == 32 - ), f"Trace ID length should be 32, got {len(trace_id2)}" + assert len(trace_id1) == 32, ( + f"Trace ID length should be 32, got {len(trace_id1)}" + ) + assert len(trace_id2) == 32, ( + f"Trace ID length should be 32, got {len(trace_id2)}" + ) # jerify it's a valid hex string int(trace_id1, 16), "Trace ID should be a valid hex string" @@ -2752,14 +2752,14 @@ def thread3_function(): assert thread3_span["trace_id"] == trace_id # Verify thread2 span is at the root level (no parent within our trace) - assert ( - thread2_span["attributes"][LangfuseOtelSpanAttributes.AS_ROOT] is True - ), "Thread 2 span should not have a parent" + assert thread2_span["attributes"][LangfuseOtelSpanAttributes.AS_ROOT] is True, ( + "Thread 2 span should not have a parent" + ) # Verify thread3 span is a child of the main span - assert ( - thread3_span["parent_span_id"] == main_span_id - ), "Thread 3 span should be a child of main span" + assert thread3_span["parent_span_id"] == main_span_id, ( + "Thread 3 span should be a child of main span" + ) @pytest.mark.asyncio async def test_span_metadata_updates_in_async_context( @@ -2918,12 +2918,12 @@ def test_metrics_and_timing(self, langfuse_client, memory_exporter): # The span timing should be within our manually recorded range # Note: This might fail on slow systems, so we use a relaxed comparison - assert ( - span_start_seconds <= end_time - ), "Span start time should be before our recorded end time" - assert ( - span_end_seconds >= start_time - ), "Span end time should be after our recorded start time" + assert span_start_seconds <= end_time, ( + "Span start time should be before our recorded end time" + ) + assert span_end_seconds >= start_time, ( + "Span end time should be after our recorded start time" + ) # Span duration should be positive and roughly match our sleep time span_duration_seconds = ( @@ -2933,9 +2933,9 @@ def test_metrics_and_timing(self, langfuse_client, memory_exporter): # Since we slept for 0.1 seconds, the span duration should be at least 0.05 seconds # but we'll be generous with the upper bound due to potential system delays - assert ( - span_duration_seconds >= 0.05 - ), f"Span duration ({span_duration_seconds}s) should be at least 0.05s" + assert span_duration_seconds >= 0.05, ( + f"Span duration ({span_duration_seconds}s) should be at least 0.05s" + ) # Add tests for media functionality in its own class @@ -3129,9 +3129,9 @@ def mask_sensitive_data(data): # Run all test cases for i, test_case in enumerate(test_cases): result = mask_sensitive_data(test_case["input"]) - assert ( - result == test_case["expected"] - ), f"Test case {i} failed: {result} != {test_case['expected']}" + assert result == test_case["expected"], ( + f"Test case {i} failed: {result} != {test_case['expected']}" + ) # Now test using the actual LangfuseSpan implementation from unittest.mock import MagicMock diff --git a/tests/test_prompt_atexit.py b/tests/test_prompt_atexit.py index 9f8838adb..2eac27ceb 100644 --- a/tests/test_prompt_atexit.py +++ b/tests/test_prompt_atexit.py @@ -50,9 +50,9 @@ def wait_2_sec(): print(process.stderr) shutdown_count = logs.count("Shutdown of prompt refresh task manager completed.") - assert ( - shutdown_count == 1 - ), f"Expected 1 shutdown messages, but found {shutdown_count}" + assert shutdown_count == 1, ( + f"Expected 1 shutdown messages, but found {shutdown_count}" + ) @pytest.mark.timeout(10) @@ -114,6 +114,6 @@ async def run_multiple_mains(): print(process.stderr) shutdown_count = logs.count("Shutdown of prompt refresh task manager completed.") - assert ( - shutdown_count == 3 - ), f"Expected 3 shutdown messages, but found {shutdown_count}" + assert shutdown_count == 3, ( + f"Expected 3 shutdown messages, but found {shutdown_count}" + ) diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py index 16a960c1f..affa84dd2 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/test_propagate_attributes.py @@ -68,9 +68,9 @@ def verify_missing_attribute(self, span_data: dict, attr_key: str): AssertionError: If the attribute exists on the span """ attributes = span_data["attributes"] - assert ( - attr_key not in attributes - ), f"Attribute '{attr_key}' should NOT be on span '{span_data['name']}'" + assert attr_key not in attributes, ( + f"Attribute '{attr_key}' should NOT be on span '{span_data['name']}'" + ) class TestPropagateAttributesBasic(TestPropagateAttributesBase): From 5a21c9d0b080c0ca0dd5a897ef302d97fd6f1f5a Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:15:24 +0100 Subject: [PATCH 103/296] test: update skip comment --- tests/test_batch_evaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_batch_evaluation.py b/tests/test_batch_evaluation.py index 46bda13d6..9bf18348d 100644 --- a/tests/test_batch_evaluation.py +++ b/tests/test_batch_evaluation.py @@ -25,7 +25,7 @@ # ============================================================================ -pytestmark = pytest.mark.skip(reason="Reason for skipping this file") +pytestmark = pytest.mark.skip(reason="Github CI runner overwhelmed by score volume") @pytest.fixture From 03bb0d1298b7f12e1e33543735ebeee8462684bf Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:15:49 +0100 Subject: [PATCH 104/296] chore: release v3.10.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 58ea94dd5..af5303a3b 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.9.3" +__version__ = "3.10.0" diff --git a/pyproject.toml b/pyproject.toml index fc5a0225a..31a7df4e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.9.3" +version = "3.10.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From c91f5aec700bb55a17e9fe61d9bd0a0a70bfce60 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 14 Nov 2025 14:30:05 +0100 Subject: [PATCH 105/296] feat(api): update API spec from langfuse/langfuse e1f390d (#1439) Co-authored-by: langfuse-bot --- langfuse/api/README.md | 12 +++ langfuse/api/reference.md | 16 +++ .../media/types/media_content_type.py | 98 +++++++++++++++++++ langfuse/api/resources/score_v_2/client.py | 20 ++++ 4 files changed, 146 insertions(+) diff --git a/langfuse/api/README.md b/langfuse/api/README.md index 9e8fef6d4..47de89ea3 100644 --- a/langfuse/api/README.md +++ b/langfuse/api/README.md @@ -5,6 +5,18 @@ The Langfuse Python library provides convenient access to the Langfuse APIs from Python. +## Table of Contents + +- [Installation](#installation) +- [Usage](#usage) +- [Async Client](#async-client) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Client](#custom-client) +- [Contributing](#contributing) + ## Installation ```sh diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 79be952da..352193f10 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -6486,6 +6486,22 @@ client.score_v_2.get()
+**dataset_run_id:** `typing.Optional[str]` — Retrieve only scores with a specific datasetRunId. + +
+
+ +
+
+ +**trace_id:** `typing.Optional[str]` — Retrieve only scores with a specific traceId. + +
+
+ +
+
+ **queue_id:** `typing.Optional[str]` — Retrieve only scores with a specific annotation queueId.
diff --git a/langfuse/api/resources/media/types/media_content_type.py b/langfuse/api/resources/media/types/media_content_type.py index e8fdeefa2..6942482bb 100644 --- a/langfuse/api/resources/media/types/media_content_type.py +++ b/langfuse/api/resources/media/types/media_content_type.py @@ -19,6 +19,8 @@ class MediaContentType(str, enum.Enum): IMAGE_SVG_XML = "image/svg+xml" IMAGE_TIFF = "image/tiff" IMAGE_BMP = "image/bmp" + IMAGE_AVIF = "image/avif" + IMAGE_HEIC = "image/heic" AUDIO_MPEG = "audio/mpeg" AUDIO_MP_3 = "audio/mp3" AUDIO_WAV = "audio/wav" @@ -27,19 +29,46 @@ class MediaContentType(str, enum.Enum): AUDIO_AAC = "audio/aac" AUDIO_MP_4 = "audio/mp4" AUDIO_FLAC = "audio/flac" + AUDIO_OPUS = "audio/opus" + AUDIO_WEBM = "audio/webm" VIDEO_MP_4 = "video/mp4" VIDEO_WEBM = "video/webm" + VIDEO_OGG = "video/ogg" + VIDEO_MPEG = "video/mpeg" + VIDEO_QUICKTIME = "video/quicktime" + VIDEO_X_MSVIDEO = "video/x-msvideo" + VIDEO_X_MATROSKA = "video/x-matroska" TEXT_PLAIN = "text/plain" TEXT_HTML = "text/html" TEXT_CSS = "text/css" TEXT_CSV = "text/csv" + TEXT_MARKDOWN = "text/markdown" + TEXT_X_PYTHON = "text/x-python" + APPLICATION_JAVASCRIPT = "application/javascript" + TEXT_X_TYPESCRIPT = "text/x-typescript" + APPLICATION_X_YAML = "application/x-yaml" APPLICATION_PDF = "application/pdf" APPLICATION_MSWORD = "application/msword" APPLICATION_MS_EXCEL = "application/vnd.ms-excel" + APPLICATION_OPENXML_SPREADSHEET = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ) APPLICATION_ZIP = "application/zip" APPLICATION_JSON = "application/json" APPLICATION_XML = "application/xml" APPLICATION_OCTET_STREAM = "application/octet-stream" + APPLICATION_OPENXML_WORD = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + APPLICATION_OPENXML_PRESENTATION = ( + "application/vnd.openxmlformats-officedocument.presentationml.presentation" + ) + APPLICATION_RTF = "application/rtf" + APPLICATION_X_NDJSON = "application/x-ndjson" + APPLICATION_PARQUET = "application/vnd.apache.parquet" + APPLICATION_GZIP = "application/gzip" + APPLICATION_X_TAR = "application/x-tar" + APPLICATION_X_7_Z_COMPRESSED = "application/x-7z-compressed" def visit( self, @@ -51,6 +80,8 @@ def visit( image_svg_xml: typing.Callable[[], T_Result], image_tiff: typing.Callable[[], T_Result], image_bmp: typing.Callable[[], T_Result], + image_avif: typing.Callable[[], T_Result], + image_heic: typing.Callable[[], T_Result], audio_mpeg: typing.Callable[[], T_Result], audio_mp_3: typing.Callable[[], T_Result], audio_wav: typing.Callable[[], T_Result], @@ -59,19 +90,40 @@ def visit( audio_aac: typing.Callable[[], T_Result], audio_mp_4: typing.Callable[[], T_Result], audio_flac: typing.Callable[[], T_Result], + audio_opus: typing.Callable[[], T_Result], + audio_webm: typing.Callable[[], T_Result], video_mp_4: typing.Callable[[], T_Result], video_webm: typing.Callable[[], T_Result], + video_ogg: typing.Callable[[], T_Result], + video_mpeg: typing.Callable[[], T_Result], + video_quicktime: typing.Callable[[], T_Result], + video_x_msvideo: typing.Callable[[], T_Result], + video_x_matroska: typing.Callable[[], T_Result], text_plain: typing.Callable[[], T_Result], text_html: typing.Callable[[], T_Result], text_css: typing.Callable[[], T_Result], text_csv: typing.Callable[[], T_Result], + text_markdown: typing.Callable[[], T_Result], + text_x_python: typing.Callable[[], T_Result], + application_javascript: typing.Callable[[], T_Result], + text_x_typescript: typing.Callable[[], T_Result], + application_x_yaml: typing.Callable[[], T_Result], application_pdf: typing.Callable[[], T_Result], application_msword: typing.Callable[[], T_Result], application_ms_excel: typing.Callable[[], T_Result], + application_openxml_spreadsheet: typing.Callable[[], T_Result], application_zip: typing.Callable[[], T_Result], application_json: typing.Callable[[], T_Result], application_xml: typing.Callable[[], T_Result], application_octet_stream: typing.Callable[[], T_Result], + application_openxml_word: typing.Callable[[], T_Result], + application_openxml_presentation: typing.Callable[[], T_Result], + application_rtf: typing.Callable[[], T_Result], + application_x_ndjson: typing.Callable[[], T_Result], + application_parquet: typing.Callable[[], T_Result], + application_gzip: typing.Callable[[], T_Result], + application_x_tar: typing.Callable[[], T_Result], + application_x_7_z_compressed: typing.Callable[[], T_Result], ) -> T_Result: if self is MediaContentType.IMAGE_PNG: return image_png() @@ -89,6 +141,10 @@ def visit( return image_tiff() if self is MediaContentType.IMAGE_BMP: return image_bmp() + if self is MediaContentType.IMAGE_AVIF: + return image_avif() + if self is MediaContentType.IMAGE_HEIC: + return image_heic() if self is MediaContentType.AUDIO_MPEG: return audio_mpeg() if self is MediaContentType.AUDIO_MP_3: @@ -105,10 +161,24 @@ def visit( return audio_mp_4() if self is MediaContentType.AUDIO_FLAC: return audio_flac() + if self is MediaContentType.AUDIO_OPUS: + return audio_opus() + if self is MediaContentType.AUDIO_WEBM: + return audio_webm() if self is MediaContentType.VIDEO_MP_4: return video_mp_4() if self is MediaContentType.VIDEO_WEBM: return video_webm() + if self is MediaContentType.VIDEO_OGG: + return video_ogg() + if self is MediaContentType.VIDEO_MPEG: + return video_mpeg() + if self is MediaContentType.VIDEO_QUICKTIME: + return video_quicktime() + if self is MediaContentType.VIDEO_X_MSVIDEO: + return video_x_msvideo() + if self is MediaContentType.VIDEO_X_MATROSKA: + return video_x_matroska() if self is MediaContentType.TEXT_PLAIN: return text_plain() if self is MediaContentType.TEXT_HTML: @@ -117,12 +187,24 @@ def visit( return text_css() if self is MediaContentType.TEXT_CSV: return text_csv() + if self is MediaContentType.TEXT_MARKDOWN: + return text_markdown() + if self is MediaContentType.TEXT_X_PYTHON: + return text_x_python() + if self is MediaContentType.APPLICATION_JAVASCRIPT: + return application_javascript() + if self is MediaContentType.TEXT_X_TYPESCRIPT: + return text_x_typescript() + if self is MediaContentType.APPLICATION_X_YAML: + return application_x_yaml() if self is MediaContentType.APPLICATION_PDF: return application_pdf() if self is MediaContentType.APPLICATION_MSWORD: return application_msword() if self is MediaContentType.APPLICATION_MS_EXCEL: return application_ms_excel() + if self is MediaContentType.APPLICATION_OPENXML_SPREADSHEET: + return application_openxml_spreadsheet() if self is MediaContentType.APPLICATION_ZIP: return application_zip() if self is MediaContentType.APPLICATION_JSON: @@ -131,3 +213,19 @@ def visit( return application_xml() if self is MediaContentType.APPLICATION_OCTET_STREAM: return application_octet_stream() + if self is MediaContentType.APPLICATION_OPENXML_WORD: + return application_openxml_word() + if self is MediaContentType.APPLICATION_OPENXML_PRESENTATION: + return application_openxml_presentation() + if self is MediaContentType.APPLICATION_RTF: + return application_rtf() + if self is MediaContentType.APPLICATION_X_NDJSON: + return application_x_ndjson() + if self is MediaContentType.APPLICATION_PARQUET: + return application_parquet() + if self is MediaContentType.APPLICATION_GZIP: + return application_gzip() + if self is MediaContentType.APPLICATION_X_TAR: + return application_x_tar() + if self is MediaContentType.APPLICATION_X_7_Z_COMPRESSED: + return application_x_7_z_compressed() diff --git a/langfuse/api/resources/score_v_2/client.py b/langfuse/api/resources/score_v_2/client.py index e927b6c2b..04569dc3a 100644 --- a/langfuse/api/resources/score_v_2/client.py +++ b/langfuse/api/resources/score_v_2/client.py @@ -41,6 +41,8 @@ def get( score_ids: typing.Optional[str] = None, config_id: typing.Optional[str] = None, session_id: typing.Optional[str] = None, + dataset_run_id: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, @@ -90,6 +92,12 @@ def get( session_id : typing.Optional[str] Retrieve only scores with a specific sessionId. + dataset_run_id : typing.Optional[str] + Retrieve only scores with a specific datasetRunId. + + trace_id : typing.Optional[str] + Retrieve only scores with a specific traceId. + queue_id : typing.Optional[str] Retrieve only scores with a specific annotation queueId. @@ -141,6 +149,8 @@ def get( "scoreIds": score_ids, "configId": config_id, "sessionId": session_id, + "datasetRunId": dataset_run_id, + "traceId": trace_id, "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, @@ -259,6 +269,8 @@ async def get( score_ids: typing.Optional[str] = None, config_id: typing.Optional[str] = None, session_id: typing.Optional[str] = None, + dataset_run_id: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, @@ -308,6 +320,12 @@ async def get( session_id : typing.Optional[str] Retrieve only scores with a specific sessionId. + dataset_run_id : typing.Optional[str] + Retrieve only scores with a specific datasetRunId. + + trace_id : typing.Optional[str] + Retrieve only scores with a specific traceId. + queue_id : typing.Optional[str] Retrieve only scores with a specific annotation queueId. @@ -367,6 +385,8 @@ async def main() -> None: "scoreIds": score_ids, "configId": config_id, "sessionId": session_id, + "datasetRunId": dataset_run_id, + "traceId": trace_id, "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, From 81bca7765d53bf9d303b31126166456539d5c5ae Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Sun, 16 Nov 2025 13:26:27 +0100 Subject: [PATCH 106/296] feat(api): update API spec from langfuse/langfuse 53b8ec8 (#1441) Co-authored-by: langfuse-bot --- langfuse/api/reference.md | 10 ++++++++-- langfuse/api/resources/prompt_version/client.py | 6 ++++-- langfuse/api/resources/prompts/client.py | 6 ++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 352193f10..e9539c90a 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -5123,7 +5123,10 @@ client.prompt_version.update(
-**name:** `str` — The name of the prompt +**name:** `str` + +The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), +the folder path must be URL encoded.
@@ -5215,7 +5218,10 @@ client.prompts.get(
-**prompt_name:** `str` — The name of the prompt +**prompt_name:** `str` + +The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), +the folder path must be URL encoded.
diff --git a/langfuse/api/resources/prompt_version/client.py b/langfuse/api/resources/prompt_version/client.py index 89140941d..5387c6e25 100644 --- a/langfuse/api/resources/prompt_version/client.py +++ b/langfuse/api/resources/prompt_version/client.py @@ -37,7 +37,8 @@ def update( Parameters ---------- name : str - The name of the prompt + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. version : int Version of the prompt to update @@ -122,7 +123,8 @@ async def update( Parameters ---------- name : str - The name of the prompt + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. version : int Version of the prompt to update diff --git a/langfuse/api/resources/prompts/client.py b/langfuse/api/resources/prompts/client.py index c38c20156..793cf3f77 100644 --- a/langfuse/api/resources/prompts/client.py +++ b/langfuse/api/resources/prompts/client.py @@ -41,7 +41,8 @@ def get( Parameters ---------- prompt_name : str - The name of the prompt + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. version : typing.Optional[int] Version of the prompt to be retrieved. @@ -310,7 +311,8 @@ async def get( Parameters ---------- prompt_name : str - The name of the prompt + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. version : typing.Optional[int] Version of the prompt to be retrieved. From 78cdbb1d2166b8dadd64278f6a20b7b95028e4a4 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 17 Nov 2025 13:25:36 +0100 Subject: [PATCH 107/296] feat(api): update API spec from langfuse/langfuse 34d42bc (#1443) Co-authored-by: langfuse-bot --- langfuse/api/reference.md | 4 ---- langfuse/api/resources/observations/client.py | 8 -------- 2 files changed, 12 deletions(-) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index e9539c90a..7c434f7d2 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -3671,10 +3671,6 @@ Each filter condition has the following structure: ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. -### Scores (requires join with scores table) -- `scores_avg` (number) - Average of numeric scores (alias: `scores`) -- `score_categories` (categoryOptions) - Categorical score values - ### Associated Trace Fields (requires join with traces table) - `userId` (string) - User ID from associated trace - `traceName` (string) - Name from associated trace diff --git a/langfuse/api/resources/observations/client.py b/langfuse/api/resources/observations/client.py index 83cc3274b..cac4efe4d 100644 --- a/langfuse/api/resources/observations/client.py +++ b/langfuse/api/resources/observations/client.py @@ -210,10 +210,6 @@ def get_many( ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Scores (requires join with scores table) - - `scores_avg` (number) - Average of numeric scores (alias: `scores`) - - `score_categories` (categoryOptions) - Categorical score values - ### Associated Trace Fields (requires join with traces table) - `userId` (string) - User ID from associated trace - `traceName` (string) - Name from associated trace @@ -515,10 +511,6 @@ async def get_many( ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Scores (requires join with scores table) - - `scores_avg` (number) - Average of numeric scores (alias: `scores`) - - `score_categories` (categoryOptions) - Categorical score values - ### Associated Trace Fields (requires join with traces table) - `userId` (string) - User ID from associated trace - `traceName` (string) - Name from associated trace From e96903613139aca8f907cf237e65b25df95c2b74 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:49:32 +0100 Subject: [PATCH 108/296] fix(client): url encode dataset name (#1445) --- langfuse/_client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 3a432da25..4c76f11e2 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2430,7 +2430,7 @@ def get_dataset( """ try: langfuse_logger.debug(f"Getting datasets {name}") - dataset = self.api.datasets.get(dataset_name=name) + dataset = self.api.datasets.get(dataset_name=self._url_encode(name)) dataset_items = [] page = 1 From 608c64a0ffd3b5d36ecba3f0ab17948d25f05133 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:49:53 +0100 Subject: [PATCH 109/296] chore: release v3.10.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index af5303a3b..c6c2bafdf 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.0" +__version__ = "3.10.1" diff --git a/pyproject.toml b/pyproject.toml index 31a7df4e7..fddc2e362 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.0" +version = "3.10.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 5e6097cfb88b2346bfd167b8270363f413688409 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:51:36 +0100 Subject: [PATCH 110/296] docs(experiments): evaluation value cannot be None (#1446) --- langfuse/experiment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/langfuse/experiment.py b/langfuse/experiment.py index f4c913c37..dc53a7d77 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -107,7 +107,6 @@ class Evaluation: - Numeric (int/float): For quantitative metrics like accuracy (0.85), BLEU (0.42) - String: For categorical results like "positive", "negative", "neutral" - Boolean: For binary assessments like "passes_safety_check" - - None: When evaluation cannot be computed (missing data, API errors, etc.) comment: Optional human-readable explanation of the evaluation result. Useful for providing context, explaining scoring rationale, or noting special conditions. Displayed in Langfuse UI for interpretability. @@ -186,7 +185,7 @@ def __init__( self, *, name: str, - value: Union[int, float, str, bool, None], + value: Union[int, float, str, bool], comment: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, data_type: Optional[ScoreDataType] = None, From ccb41c04419746abdbe2ee26fbdbd0bc55b2b4dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:51:30 +0000 Subject: [PATCH 111/296] chore(deps-dev): bump langchain-core from 1.0.0 to 1.0.7 (#1447) Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 1.0.0 to 1.0.7. - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.0.0...langchain-core==1.0.7) --- updated-dependencies: - dependency-name: langchain-core dependency-version: 1.0.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index a8d49f84f..bace2ae49 100644 --- a/poetry.lock +++ b/poetry.lock @@ -709,14 +709,14 @@ xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "1.0.0" +version = "1.0.7" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["dev"] files = [ - {file = "langchain_core-1.0.0-py3-none-any.whl", hash = "sha256:a94561bf75dd097c7d6e3864950f28dadc963f0bd810114de4095f41f634059b"}, - {file = "langchain_core-1.0.0.tar.gz", hash = "sha256:8e81c94a22fa3a362a0f101bbd1271bf3725e50cf1e31c84e8f4a1c731279785"}, + {file = "langchain_core-1.0.7-py3-none-any.whl", hash = "sha256:76af258b0e95a7915b8e301706a45ded50c75b80ff35329394d4df964416e32a"}, + {file = "langchain_core-1.0.7.tar.gz", hash = "sha256:6c64399cb0f163a7e45a764cce75d80fd08b82f4e0274ca892cfbcaa2f29200b"}, ] [package.dependencies] From 96b2bf8b21fe96ee40c301809842b12341febaf7 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:32:19 +0100 Subject: [PATCH 112/296] fix(openai): handle pydantic BaseModel as metadata (#1449) --- langfuse/openai.py | 5 ++++- tests/test_openai.py | 10 ---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 1a63835d4..c9d277fa4 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -400,7 +400,10 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A and not isinstance(metadata, NotGiven) and not isinstance(metadata, dict) ): - raise TypeError("metadata must be a dictionary") + if isinstance(metadata, BaseModel): + metadata = metadata.model_dump() + else: + metadata = {} model = kwargs.get("model", None) or None diff --git a/tests/test_openai.py b/tests/test_openai.py index b6bcf29d6..f24bf93cf 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -607,16 +607,6 @@ def test_fails_wrong_name(openai): ) -def test_fails_wrong_metadata(openai): - with pytest.raises(TypeError, match="metadata must be a dictionary"): - openai.OpenAI().completions.create( - metadata="metadata", - model="gpt-3.5-turbo-instruct", - prompt="1 + 1 = ", - temperature=0, - ) - - def test_fails_wrong_trace_id(openai): with pytest.raises(TypeError, match="trace_id must be a string"): openai.OpenAI().completions.create( From e8b355e249e6c1ecd9ee4082983e0f75bbb31d6b Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:48:14 +0100 Subject: [PATCH 113/296] fix(datasets): add schema arguments to create_dataset (#1457) * fix(datasets): add schema arguments to create_dataset * fix tests --- langfuse/_client/client.py | 10 +++++++++- tests/test_datasets.py | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 4c76f11e2..36da0e4bf 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -3250,6 +3250,8 @@ def create_dataset( name: str, description: Optional[str] = None, metadata: Optional[Any] = None, + input_schema: Optional[Any] = None, + expected_output_schema: Optional[Any] = None, ) -> Dataset: """Create a dataset with the given name on Langfuse. @@ -3257,13 +3259,19 @@ def create_dataset( name: Name of the dataset to create. description: Description of the dataset. Defaults to None. metadata: Additional metadata. Defaults to None. + input_schema: JSON Schema for validating dataset item inputs. When set, all new items will be validated against this schema. + expected_output_schema: JSON Schema for validating dataset item expected outputs. When set, all new items will be validated against this schema. Returns: Dataset: The created dataset as returned by the Langfuse API. """ try: body = CreateDatasetRequest( - name=name, description=description, metadata=metadata + name=name, + description=description, + metadata=metadata, + inputSchema=input_schema, + expectedOutputSchema=expected_output_schema, ) langfuse_logger.debug(f"Creating datasets {body}") diff --git a/tests/test_datasets.py b/tests/test_datasets.py index c64a4adc1..c3ad7a318 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -146,7 +146,7 @@ def test_dataset_run_with_metadata_and_description(): dataset_name = create_uuid() langfuse.create_dataset(name=dataset_name) - input = json.dumps({"input": "Hello World"}) + input = {"input": "Hello World"} langfuse.create_dataset_item(dataset_name=dataset_name, input=input) dataset = langfuse.get_dataset(dataset_name) @@ -187,7 +187,7 @@ def test_get_dataset_runs(): dataset_name = create_uuid() langfuse.create_dataset(name=dataset_name) - input = json.dumps({"input": "Hello World"}) + input = {"input": "Hello World"} langfuse.create_dataset_item(dataset_name=dataset_name, input=input) dataset = langfuse.get_dataset(dataset_name) From 31687037b48a9c666df8aaa0d9bfc60357ad55aa Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:00:33 +0100 Subject: [PATCH 114/296] fix(prompts): evict prompt cache on NotFound error (#1442) (#1456) fix: evict prompt cache on NotFound error (#1442) - Add NotFoundError handling in _fetch_prompt_and_update_cache - Add delete() method to PromptCache - Add test for cache eviction on NotFound - Fix test fixture to initialize _resources with PromptCache Co-authored-by: Betensis <60091030+Betensis@users.noreply.github.com> --- langfuse/_client/client.py | 9 ++++ langfuse/_utils/prompt_cache.py | 3 ++ tests/test_prompt.py | 73 +++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 36da0e4bf..46586f3b0 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -79,6 +79,7 @@ from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache from langfuse.api.resources.commons.errors.error import Error +from langfuse.api.resources.commons.errors.not_found_error import NotFoundError from langfuse.api.resources.ingestion.types.score_body import ScoreBody from langfuse.api.resources.prompts.types import ( CreatePromptRequest_Chat, @@ -3605,6 +3606,14 @@ def fetch_prompts() -> Any: return prompt + except NotFoundError as not_found_error: + langfuse_logger.warning( + f"Prompt '{cache_key}' not found during refresh, evicting from cache." + ) + if self._resources is not None: + self._resources.prompt_cache.delete(cache_key) + raise not_found_error + except Exception as e: langfuse_logger.error( f"Error while fetching prompt '{cache_key}': {str(e)}" diff --git a/langfuse/_utils/prompt_cache.py b/langfuse/_utils/prompt_cache.py index 919333b6b..09d78bc8b 100644 --- a/langfuse/_utils/prompt_cache.py +++ b/langfuse/_utils/prompt_cache.py @@ -158,6 +158,9 @@ def set(self, key: str, value: PromptClient, ttl_seconds: Optional[int]) -> None self._cache[key] = PromptCacheItem(value, ttl_seconds) + def delete(self, key: str) -> None: + self._cache.pop(key, None) + def invalidate(self, prompt_name: str) -> None: """Invalidate all cached prompts with the given prompt name.""" for key in list(self._cache): diff --git a/tests/test_prompt.py b/tests/test_prompt.py index e5346debf..bc3a5b7eb 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -7,8 +7,10 @@ from langfuse._client.client import Langfuse from langfuse._utils.prompt_cache import ( DEFAULT_PROMPT_CACHE_TTL_SECONDS, + PromptCache, PromptCacheItem, ) +from langfuse.api.resources.commons.errors.not_found_error import NotFoundError from langfuse.api.resources.prompts import Prompt_Chat, Prompt_Text from langfuse.model import ChatPromptClient, TextPromptClient from tests.utils import create_uuid, get_api @@ -679,9 +681,15 @@ def test_prompt_end_to_end(): @pytest.fixture def langfuse(): + from langfuse._client.resource_manager import LangfuseResourceManager + langfuse_instance = Langfuse() langfuse_instance.api = Mock() + if langfuse_instance._resources is None: + langfuse_instance._resources = Mock(spec=LangfuseResourceManager) + langfuse_instance._resources.prompt_cache = PromptCache() + return langfuse_instance @@ -1157,6 +1165,71 @@ def test_get_expired_prompt_when_failing_fetch(mock_time, langfuse: Langfuse): assert result_call_2 == prompt_client +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_evict_prompt_cache_entry_when_refresh_returns_not_found( + mock_time, langfuse: Langfuse +) -> None: + mock_time.return_value = 0 + + prompt_name = "test_evict_prompt_cache_entry_when_refresh_returns_not_found" + ttl_seconds = 5 + fallback_prompt = "fallback text prompt" + + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + cache_key = PromptCache.generate_cache_key(prompt_name, version=None, label=None) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + initial_result = langfuse.get_prompt( + prompt_name, + cache_ttl_seconds=ttl_seconds, + max_retries=0, + ) + assert initial_result == prompt_client + assert langfuse._resources.prompt_cache.get(cache_key) is not None + + # Expire cache entry and trigger background refresh + mock_time.return_value = ttl_seconds + 1 + + def raise_not_found(*_args: object, **_kwargs: object) -> None: + raise NotFoundError({"message": "Prompt not found"}) + + mock_server_call.side_effect = raise_not_found + + stale_result = langfuse.get_prompt( + prompt_name, + cache_ttl_seconds=ttl_seconds, + max_retries=0, + ) + assert stale_result == prompt_client + + while True: + if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: + break + sleep(0.1) + + assert langfuse._resources.prompt_cache.get(cache_key) is None + + fallback_result = langfuse.get_prompt( + prompt_name, + cache_ttl_seconds=ttl_seconds, + fallback=fallback_prompt, + max_retries=0, + ) + assert fallback_result.is_fallback + assert fallback_result.prompt == fallback_prompt + + # Should fetch new prompt if version changes def test_get_fresh_prompt_when_version_changes(langfuse: Langfuse): prompt_name = "test_get_fresh_prompt_when_version_changes" From 478e7e2e6f539433e7259c1e95edb953ebd772fd Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:02:29 +0100 Subject: [PATCH 115/296] chore: release v3.10.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index c6c2bafdf..076dc6747 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.1" +__version__ = "3.10.2" diff --git a/pyproject.toml b/pyproject.toml index fddc2e362..eeb2a8a8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.1" +version = "3.10.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From d233bc4557c9b8bb6b603ab1b2b831f3479e65e4 Mon Sep 17 00:00:00 2001 From: aablsk <70068680+aablsk@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:08:03 +0100 Subject: [PATCH 116/296] fix(gemini): gemini input token calculation when implicit cache is hit using langchain (#1451) fix: gemini caching token calculation when using langchain Currently: When `input_modality_1` contains tokens, `input` token count is 0. The cached token logic only subtracts cached tokens from `input`, when they should be subtracted from the `input_modality_1`. Proposed fix: Subtract `cache_tokens_details` from the corresponding `input_modality` in addition to subtracting from `input`. --- langfuse/langchain/CallbackHandler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 9e8278e28..f6c3ed92c 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1175,6 +1175,9 @@ def _parse_usage_model(usage: Union[pydantic.BaseModel, dict]) -> Any: if "input" in usage_model: usage_model["input"] = max(0, usage_model["input"] - value) + if f"input_modality_{item['modality']}" in usage_model: + usage_model[f"input_modality_{item['modality']}"] = max(0, usage_model[f"input_modality_{item['modality']}"] - value) + usage_model = {k: v for k, v in usage_model.items() if isinstance(v, int)} return usage_model if usage_model else None From 8ceb2368aed5c3028437dafd7b14d2490a6d62af Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 1 Dec 2025 15:16:40 +0100 Subject: [PATCH 117/296] feat(api): update API spec from langfuse/langfuse ad16fa0 (#1454) Co-authored-by: langfuse-bot Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/api/__init__.py | 8 ++ langfuse/api/resources/__init__.py | 8 ++ langfuse/api/resources/commons/__init__.py | 8 ++ .../api/resources/commons/types/__init__.py | 8 ++ langfuse/api/resources/commons/types/model.py | 32 ++++- .../resources/commons/types/pricing_tier.py | 117 ++++++++++++++++++ .../commons/types/pricing_tier_condition.py | 92 ++++++++++++++ .../commons/types/pricing_tier_input.py | 102 +++++++++++++++ .../commons/types/pricing_tier_operator.py | 41 ++++++ .../models/types/create_model_request.py | 31 ++++- 10 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 langfuse/api/resources/commons/types/pricing_tier.py create mode 100644 langfuse/api/resources/commons/types/pricing_tier_condition.py create mode 100644 langfuse/api/resources/commons/types/pricing_tier_input.py create mode 100644 langfuse/api/resources/commons/types/pricing_tier_operator.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 451ad991e..c958219d5 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -157,6 +157,10 @@ PaginatedSessions, PatchMediaBody, PlaceholderMessage, + PricingTier, + PricingTierCondition, + PricingTierInput, + PricingTierOperator, Project, ProjectDeletionResponse, Projects, @@ -403,6 +407,10 @@ "PaginatedSessions", "PatchMediaBody", "PlaceholderMessage", + "PricingTier", + "PricingTierCondition", + "PricingTierInput", + "PricingTierOperator", "Project", "ProjectDeletionResponse", "Projects", diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index b3a6cc31a..d91362a28 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -84,6 +84,10 @@ Observation, ObservationLevel, ObservationsView, + PricingTier, + PricingTierCondition, + PricingTierInput, + PricingTierOperator, Score, ScoreConfig, ScoreDataType, @@ -424,6 +428,10 @@ "PaginatedSessions", "PatchMediaBody", "PlaceholderMessage", + "PricingTier", + "PricingTierCondition", + "PricingTierInput", + "PricingTierOperator", "Project", "ProjectDeletionResponse", "Projects", diff --git a/langfuse/api/resources/commons/__init__.py b/langfuse/api/resources/commons/__init__.py index 6dfbecafe..77097ba49 100644 --- a/langfuse/api/resources/commons/__init__.py +++ b/langfuse/api/resources/commons/__init__.py @@ -26,6 +26,10 @@ Observation, ObservationLevel, ObservationsView, + PricingTier, + PricingTierCondition, + PricingTierInput, + PricingTierOperator, Score, ScoreConfig, ScoreDataType, @@ -82,6 +86,10 @@ "Observation", "ObservationLevel", "ObservationsView", + "PricingTier", + "PricingTierCondition", + "PricingTierInput", + "PricingTierOperator", "Score", "ScoreConfig", "ScoreDataType", diff --git a/langfuse/api/resources/commons/types/__init__.py b/langfuse/api/resources/commons/types/__init__.py index 1c0d06a8d..ee7f5714b 100644 --- a/langfuse/api/resources/commons/types/__init__.py +++ b/langfuse/api/resources/commons/types/__init__.py @@ -25,6 +25,10 @@ from .observation import Observation from .observation_level import ObservationLevel from .observations_view import ObservationsView +from .pricing_tier import PricingTier +from .pricing_tier_condition import PricingTierCondition +from .pricing_tier_input import PricingTierInput +from .pricing_tier_operator import PricingTierOperator from .score import Score, Score_Boolean, Score_Categorical, Score_Numeric from .score_config import ScoreConfig from .score_data_type import ScoreDataType @@ -63,6 +67,10 @@ "Observation", "ObservationLevel", "ObservationsView", + "PricingTier", + "PricingTierCondition", + "PricingTierInput", + "PricingTierOperator", "Score", "ScoreConfig", "ScoreDataType", diff --git a/langfuse/api/resources/commons/types/model.py b/langfuse/api/resources/commons/types/model.py index ea3922ee9..1b83c2696 100644 --- a/langfuse/api/resources/commons/types/model.py +++ b/langfuse/api/resources/commons/types/model.py @@ -7,11 +7,20 @@ from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 from .model_price import ModelPrice from .model_usage_unit import ModelUsageUnit +from .pricing_tier import PricingTier class Model(pydantic_v1.BaseModel): """ Model definition used for transforming usage into USD cost and/or tokenization. + + Models can have either simple flat pricing or tiered pricing: + - Flat pricing: Single price per usage type (legacy, but still supported) + - Tiered pricing: Multiple pricing tiers with conditional matching based on usage patterns + + The pricing tiers approach is recommended for models with usage-based pricing variations. + When using tiered pricing, the flat price fields (inputPrice, outputPrice, prices) are populated + from the default tier for backward compatibility. """ id: str @@ -73,9 +82,30 @@ class Model(pydantic_v1.BaseModel): """ is_langfuse_managed: bool = pydantic_v1.Field(alias="isLangfuseManaged") + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") + """ + Timestamp when the model was created + """ + prices: typing.Dict[str, ModelPrice] = pydantic_v1.Field() """ - Price (USD) by usage type + Deprecated. Use 'pricingTiers' instead for models with usage-based pricing variations. + + This field shows prices by usage type from the default pricing tier. Maintained for backward compatibility. + If the model uses tiered pricing, this field will be populated from the default tier's prices. + """ + + pricing_tiers: typing.List[PricingTier] = pydantic_v1.Field(alias="pricingTiers") + """ + Array of pricing tiers with conditional pricing based on usage thresholds. + + Pricing tiers enable accurate cost tracking for models that charge different rates based on usage patterns + (e.g., different rates for high-volume usage, large context windows, or cached tokens). + + Each model must have exactly one default tier (isDefault=true, priority=0) that serves as a fallback. + Additional conditional tiers can be defined with specific matching criteria. + + If this array is empty, the model uses legacy flat pricing from the inputPrice/outputPrice/totalPrice fields. """ def json(self, **kwargs: typing.Any) -> str: diff --git a/langfuse/api/resources/commons/types/pricing_tier.py b/langfuse/api/resources/commons/types/pricing_tier.py new file mode 100644 index 000000000..031d142c0 --- /dev/null +++ b/langfuse/api/resources/commons/types/pricing_tier.py @@ -0,0 +1,117 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .pricing_tier_condition import PricingTierCondition + + +class PricingTier(pydantic_v1.BaseModel): + """ + Pricing tier definition with conditional pricing based on usage thresholds. + + Pricing tiers enable accurate cost tracking for LLM providers that charge different rates based on usage patterns. + For example, some providers charge higher rates when context size exceeds certain thresholds. + + How tier matching works: + 1. Tiers are evaluated in ascending priority order (priority 1 before priority 2, etc.) + 2. The first tier where ALL conditions match is selected + 3. If no conditional tiers match, the default tier is used as a fallback + 4. The default tier has priority 0 and no conditions + + Why priorities matter: + - Lower priority numbers are evaluated first, allowing you to define specific cases before general ones + - Example: Priority 1 for "high usage" (>200K tokens), Priority 2 for "medium usage" (>100K tokens), Priority 0 for default + - Without proper ordering, a less specific condition might match before a more specific one + + Every model must have exactly one default tier to ensure cost calculation always succeeds. + """ + + id: str = pydantic_v1.Field() + """ + Unique identifier for the pricing tier + """ + + name: str = pydantic_v1.Field() + """ + Name of the pricing tier for display and identification purposes. + + Examples: "Standard", "High Volume Tier", "Large Context", "Extended Context Tier" + """ + + is_default: bool = pydantic_v1.Field(alias="isDefault") + """ + Whether this is the default tier. Every model must have exactly one default tier with priority 0 and no conditions. + + The default tier serves as a fallback when no conditional tiers match, ensuring cost calculation always succeeds. + It typically represents the base pricing for standard usage patterns. + """ + + priority: int = pydantic_v1.Field() + """ + Priority for tier matching evaluation. Lower numbers = higher priority (evaluated first). + + The default tier must always have priority 0. Conditional tiers should have priority 1, 2, 3, etc. + + Example ordering: + - Priority 0: Default tier (no conditions, always matches as fallback) + - Priority 1: High usage tier (e.g., >200K tokens) + - Priority 2: Medium usage tier (e.g., >100K tokens) + + This ensures more specific conditions are checked before general ones. + """ + + conditions: typing.List[PricingTierCondition] = pydantic_v1.Field() + """ + Array of conditions that must ALL be met for this tier to match (AND logic). + + The default tier must have an empty conditions array. Conditional tiers should have one or more conditions + that define when this tier's pricing applies. + + Multiple conditions enable complex matching scenarios (e.g., "high input tokens AND low output tokens"). + """ + + prices: typing.Dict[str, float] = pydantic_v1.Field() + """ + Prices (USD) by usage type for this tier. + + Common usage types: "input", "output", "total", "request", "image" + Prices are specified in USD per unit (e.g., per token, per request, per second). + + Example: {"input": 0.000003, "output": 0.000015} means $3 per million input tokens and $15 per million output tokens. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/pricing_tier_condition.py b/langfuse/api/resources/commons/types/pricing_tier_condition.py new file mode 100644 index 000000000..8b89fe116 --- /dev/null +++ b/langfuse/api/resources/commons/types/pricing_tier_condition.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .pricing_tier_operator import PricingTierOperator + + +class PricingTierCondition(pydantic_v1.BaseModel): + """ + Condition for matching a pricing tier based on usage details. Used to implement tiered pricing models where costs vary based on usage thresholds. + + How it works: + 1. The regex pattern matches against usage detail keys (e.g., "input_tokens", "input_cached") + 2. Values of all matching keys are summed together + 3. The sum is compared against the threshold value using the specified operator + 4. All conditions in a tier must be met (AND logic) for the tier to match + + Common use cases: + - Threshold-based pricing: Match when accumulated usage exceeds a certain amount + - Usage-type-specific pricing: Different rates for cached vs non-cached tokens, or input vs output + - Volume-based pricing: Different rates based on total request or token count + """ + + usage_detail_pattern: str = pydantic_v1.Field(alias="usageDetailPattern") + """ + Regex pattern to match against usage detail keys. All matching keys' values are summed for threshold comparison. + + Examples: + - "^input" matches "input", "input_tokens", "input_cached", etc. + - "^(input|prompt)" matches both "input_tokens" and "prompt_tokens" + - "_cache$" matches "input_cache", "output_cache", etc. + + The pattern is case-insensitive by default. If no keys match, the sum is treated as zero. + """ + + operator: PricingTierOperator = pydantic_v1.Field() + """ + Comparison operator to apply between the summed value and the threshold. + + - gt: greater than (sum > threshold) + - gte: greater than or equal (sum >= threshold) + - lt: less than (sum < threshold) + - lte: less than or equal (sum <= threshold) + - eq: equal (sum == threshold) + - neq: not equal (sum != threshold) + """ + + value: float = pydantic_v1.Field() + """ + Threshold value for comparison. For token-based pricing, this is typically the token count threshold (e.g., 200000 for a 200K token threshold). + """ + + case_sensitive: bool = pydantic_v1.Field(alias="caseSensitive") + """ + Whether the regex pattern matching is case-sensitive. Default is false (case-insensitive matching). + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/pricing_tier_input.py b/langfuse/api/resources/commons/types/pricing_tier_input.py new file mode 100644 index 000000000..00dfdb37b --- /dev/null +++ b/langfuse/api/resources/commons/types/pricing_tier_input.py @@ -0,0 +1,102 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .pricing_tier_condition import PricingTierCondition + + +class PricingTierInput(pydantic_v1.BaseModel): + """ + Input schema for creating a pricing tier. The tier ID will be automatically generated server-side. + + When creating a model with pricing tiers: + - Exactly one tier must have isDefault=true (the fallback tier) + - The default tier must have priority=0 and conditions=[] + - All tier names and priorities must be unique within the model + - Each tier must define at least one price + + See PricingTier for detailed information about how tiers work and why they're useful. + """ + + name: str = pydantic_v1.Field() + """ + Name of the pricing tier for display and identification purposes. + + Must be unique within the model. Common patterns: "Standard", "High Volume Tier", "Extended Context" + """ + + is_default: bool = pydantic_v1.Field(alias="isDefault") + """ + Whether this is the default tier. Exactly one tier per model must be marked as default. + + Requirements for default tier: + - Must have isDefault=true + - Must have priority=0 + - Must have empty conditions array (conditions=[]) + + The default tier acts as a fallback when no conditional tiers match. + """ + + priority: int = pydantic_v1.Field() + """ + Priority for tier matching evaluation. Lower numbers = higher priority (evaluated first). + + Must be unique within the model. The default tier must have priority=0. + Conditional tiers should use priority 1, 2, 3, etc. based on their specificity. + """ + + conditions: typing.List[PricingTierCondition] = pydantic_v1.Field() + """ + Array of conditions that must ALL be met for this tier to match (AND logic). + + The default tier must have an empty array (conditions=[]). + Conditional tiers should define one or more conditions that specify when this tier's pricing applies. + + Each condition specifies a regex pattern, operator, and threshold value for matching against usage details. + """ + + prices: typing.Dict[str, float] = pydantic_v1.Field() + """ + Prices (USD) by usage type for this tier. At least one price must be defined. + + Common usage types: "input", "output", "total", "request", "image" + Prices are in USD per unit (e.g., per token). + + Example: {"input": 0.000003, "output": 0.000015} represents $3 per million input tokens and $15 per million output tokens. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/pricing_tier_operator.py b/langfuse/api/resources/commons/types/pricing_tier_operator.py new file mode 100644 index 000000000..c5af10199 --- /dev/null +++ b/langfuse/api/resources/commons/types/pricing_tier_operator.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class PricingTierOperator(str, enum.Enum): + """ + Comparison operators for pricing tier conditions + """ + + GT = "gt" + GTE = "gte" + LT = "lt" + LTE = "lte" + EQ = "eq" + NEQ = "neq" + + def visit( + self, + gt: typing.Callable[[], T_Result], + gte: typing.Callable[[], T_Result], + lt: typing.Callable[[], T_Result], + lte: typing.Callable[[], T_Result], + eq: typing.Callable[[], T_Result], + neq: typing.Callable[[], T_Result], + ) -> T_Result: + if self is PricingTierOperator.GT: + return gt() + if self is PricingTierOperator.GTE: + return gte() + if self is PricingTierOperator.LT: + return lt() + if self is PricingTierOperator.LTE: + return lte() + if self is PricingTierOperator.EQ: + return eq() + if self is PricingTierOperator.NEQ: + return neq() diff --git a/langfuse/api/resources/models/types/create_model_request.py b/langfuse/api/resources/models/types/create_model_request.py index b3d8a6462..3f9f80119 100644 --- a/langfuse/api/resources/models/types/create_model_request.py +++ b/langfuse/api/resources/models/types/create_model_request.py @@ -6,6 +6,7 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 from ...commons.types.model_usage_unit import ModelUsageUnit +from ...commons.types.pricing_tier_input import PricingTierInput class CreateModelRequest(pydantic_v1.BaseModel): @@ -35,21 +36,45 @@ class CreateModelRequest(pydantic_v1.BaseModel): alias="inputPrice", default=None ) """ - Price (USD) per input unit + Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. Creates a default tier if pricingTiers not provided. """ output_price: typing.Optional[float] = pydantic_v1.Field( alias="outputPrice", default=None ) """ - Price (USD) per output unit + Deprecated. Use 'pricingTiers' instead. Price (USD) per output unit. Creates a default tier if pricingTiers not provided. """ total_price: typing.Optional[float] = pydantic_v1.Field( alias="totalPrice", default=None ) """ - Price (USD) per total units. Cannot be set if input or output price is set. + Deprecated. Use 'pricingTiers' instead. Price (USD) per total units. Cannot be set if input or output price is set. Creates a default tier if pricingTiers not provided. + """ + + pricing_tiers: typing.Optional[typing.List[PricingTierInput]] = pydantic_v1.Field( + alias="pricingTiers", default=None + ) + """ + Optional. Array of pricing tiers for this model. + + Use pricing tiers for all models - both those with threshold-based pricing variations and those with simple flat pricing: + + - For models with standard flat pricing: Create a single default tier with your prices + (e.g., one tier with isDefault=true, priority=0, conditions=[], and your standard prices) + + - For models with threshold-based pricing: Create a default tier plus additional conditional tiers + (e.g., default tier for standard usage + high-volume tier for usage above certain thresholds) + + Requirements: + - Cannot be provided with flat prices (inputPrice/outputPrice/totalPrice) - use one approach or the other + - Must include exactly one default tier with isDefault=true, priority=0, and conditions=[] + - All tier names and priorities must be unique within the model + - Each tier must define at least one price + + If omitted, you must provide flat prices instead (inputPrice/outputPrice/totalPrice), + which will automatically create a single default tier named "Standard". """ tokenizer_id: typing.Optional[str] = pydantic_v1.Field( From 2f0da0b2ff133eaeb3e079e47b8a63aa05338b60 Mon Sep 17 00:00:00 2001 From: Reiner Marquez Date: Mon, 1 Dec 2025 09:22:43 -0500 Subject: [PATCH 118/296] feat: Add comment field to langfuse Evaluation from autoevals metadata (#1452) * feat: update create_evaluator_from_autoevals to add comment field to langfuse Evaluation from autoevals metadata * Update langfuse/experiment.py Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --------- Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- langfuse/experiment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/langfuse/experiment.py b/langfuse/experiment.py index dc53a7d77..44c0bb859 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -1039,7 +1039,10 @@ def langfuse_evaluator( ) return Evaluation( - name=evaluation.name, value=evaluation.score, metadata=evaluation.metadata + name=evaluation.name, + value=evaluation.score, + comment=(evaluation.metadata or {}).get("comment"), + metadata=evaluation.metadata, ) return langfuse_evaluator From 969b832df26ced4d971ac46b68595056ab8fc0dc Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:27:39 +0100 Subject: [PATCH 119/296] feat(langchain): allow injecting trace_context into LangchainCallbackHandler (#1419) (#1458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Be able to inject trace_context into LangchainCallbackHandler (#1419) Co-authored-by: Fabian Höring --- langfuse/langchain/CallbackHandler.py | 43 ++++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index f6c3ed92c..7e23b155a 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -28,6 +28,7 @@ LangfuseSpan, LangfuseTool, ) +from langfuse.types import TraceContext from langfuse._utils import _get_timestamp from langfuse.langchain.utils import _extract_model_name from langfuse.logger import langfuse_logger @@ -92,7 +93,11 @@ class LangchainCallbackHandler(LangchainBaseCallbackHandler): def __init__( - self, *, public_key: Optional[str] = None, update_trace: bool = False + self, + *, + public_key: Optional[str] = None, + update_trace: bool = False, + trace_context: Optional[TraceContext] = None, ) -> None: """Initialize the LangchainCallbackHandler. @@ -120,6 +125,7 @@ def __init__( self.last_trace_id: Optional[str] = None self.update_trace = update_trace + self.trace_context = trace_context def on_llm_new_token( self, @@ -299,16 +305,31 @@ def on_chain_start( serialized, "chain", **kwargs ) - span = self._get_parent_observation(parent_run_id).start_observation( - name=span_name, - as_type=observation_type, - metadata=span_metadata, - input=inputs, - level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], - span_level, - ), - ) + obs = self._get_parent_observation(parent_run_id) + if isinstance(obs, Langfuse): + span = obs.start_observation( + trace_context=self.trace_context, + name=span_name, + as_type=observation_type, + metadata=span_metadata, + input=inputs, + level=cast( + Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"] | None, + span_level, + ), + ) + else: + span = obs.start_observation( + name=span_name, + as_type=observation_type, + metadata=span_metadata, + input=inputs, + level=cast( + Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"] | None, + span_level, + ), + ) + self._attach_observation(run_id, span) if parent_run_id is None: From bfbfac71ad123c73f8be039cc6eb607e39f89f95 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 1 Dec 2025 17:49:44 +0100 Subject: [PATCH 120/296] feat(api): update API spec from langfuse/langfuse cb16277 (#1459) Co-authored-by: langfuse-bot --- .../api/resources/llm_connections/types/llm_connection.py | 7 +++++++ .../llm_connections/types/upsert_llm_connection_request.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/langfuse/api/resources/llm_connections/types/llm_connection.py b/langfuse/api/resources/llm_connections/types/llm_connection.py index 0b17b97a7..6ded1459a 100644 --- a/langfuse/api/resources/llm_connections/types/llm_connection.py +++ b/langfuse/api/resources/llm_connections/types/llm_connection.py @@ -48,6 +48,13 @@ class LlmConnection(pydantic_v1.BaseModel): Keys of extra headers sent with requests (values excluded for security) """ + config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( + default=None + ) + """ + Adapter-specific configuration. Required for Bedrock (`{"region":"us-east-1"}`), optional for VertexAI (`{"location":"us-central1"}`), not used by other adapters. + """ + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") diff --git a/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py b/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py index d0a5a368d..7490f25b7 100644 --- a/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py +++ b/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py @@ -54,6 +54,13 @@ class UpsertLlmConnectionRequest(pydantic_v1.BaseModel): Extra headers to send with requests """ + config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( + default=None + ) + """ + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + """ + def json(self, **kwargs: typing.Any) -> str: kwargs_with_defaults: typing.Any = { "by_alias": True, From 863b27a20fb310e365f0503b56d03051c037aff3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:00:34 +0100 Subject: [PATCH 121/296] fix(resource-manager): reuse passed tracer provider for reinit (#1461) --- langfuse/_client/get_client.py | 1 + langfuse/_client/resource_manager.py | 1 + 2 files changed, 2 insertions(+) diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index 402801afd..da1c3c994 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -52,6 +52,7 @@ def _create_client_from_instance( mask=instance.mask, blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes, additional_headers=instance.additional_headers, + tracer_provider=instance.tracer_provider, ) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index f0c4663d5..23b6ee557 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -179,6 +179,7 @@ def _initialize_instance( tracer_provider = tracer_provider or _init_tracer_provider( environment=environment, release=release, sample_rate=sample_rate ) + self.tracer_provider = tracer_provider langfuse_processor = LangfuseSpanProcessor( public_key=self.public_key, From 271a0beb7413860e167bc68ae57c08fb3772c6ad Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:00:55 +0100 Subject: [PATCH 122/296] chore: release v3.10.3 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 076dc6747..f48b225b4 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.2" +__version__ = "3.10.3" diff --git a/pyproject.toml b/pyproject.toml index eeb2a8a8b..de7caa2a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.2" +version = "3.10.3" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 4811e543049f98b1b6eaff8ddc4655b8bc08cde9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 05:10:05 +0000 Subject: [PATCH 123/296] chore(deps-dev): bump werkzeug from 3.1.3 to 3.1.4 (#1462) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.1.3...3.1.4) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index bace2ae49..a54e5fe7e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2490,18 +2490,18 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "werkzeug" -version = "3.1.3" +version = "3.1.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, - {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, + {file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"}, + {file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"}, ] [package.dependencies] -MarkupSafe = ">=2.1.1" +markupsafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] From 374505a149bce0633fa23afebcb9595f4ed084cc Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:51:50 +0100 Subject: [PATCH 124/296] chore: update release script --- .github/workflows/release.yml | 437 +++++++++++++++++++++++++++++++++- CLAUDE.md | 16 +- pyproject.toml | 4 - scripts/release.py | 184 -------------- 4 files changed, 437 insertions(+), 204 deletions(-) delete mode 100644 scripts/release.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0aa2f8d52..758b546cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,436 @@ +name: Release Python SDK + on: workflow_dispatch: - push: - # Pattern matched against refs/tags - tags: - - "v[0-9]+.[0-9]+.[0-9]+" # Semantic version tags + inputs: + version: + description: "Version bump type" + required: true + type: choice + options: + - patch + - minor + - major + - prerelease + prerelease_type: + description: 'Pre-release type (only used if version is "prerelease")' + type: choice + default: "" + options: + - "" + - alpha + - beta + - rc + prerelease_increment: + description: 'Pre-release number (e.g., 1 for alpha1). Leave empty to auto-increment or start at 1.' + type: string + default: "" + +permissions: + contents: write + id-token: write # Required for PyPI Trusted Publishing via OIDC + +concurrency: + group: python-sdk-release + cancel-in-progress: false jobs: - release: + release-python-sdk: runs-on: ubuntu-latest - environment: "protected branches" + environment: protected branches steps: - - uses: actions/checkout@v4 + - name: Verify branch + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "❌ Error: Releases can only be triggered from main branch" + echo "Current ref: ${{ github.ref }}" + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@v4 with: - ref: main # Always checkout main even for tagged releases fetch-depth: 0 token: ${{ secrets.GH_ACCESS_TOKEN }} - - name: Push to production - run: git push origin +main:production + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: "1.8.4" + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Configure Git + env: + GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} + run: | + git config user.name "langfuse-bot" + git config user.email "langfuse-bot@langfuse.com" + + echo "$GH_ACCESS_TOKEN" | gh auth login --with-token + gh auth setup-git + + - name: Get current version + id: current-version + run: | + current_version=$(poetry version -s) + echo "version=$current_version" >> $GITHUB_OUTPUT + echo "Current version: $current_version" + + - name: Calculate new version + id: new-version + run: | + current_version="${{ steps.current-version.outputs.version }}" + version_type="${{ inputs.version }}" + prerelease_type="${{ inputs.prerelease_type }}" + prerelease_increment="${{ inputs.prerelease_increment }}" + + # Extract base version (strip any pre-release suffix like a1, b2, rc1) + base_version=$(echo "$current_version" | sed -E 's/(a|b|rc)[0-9]+$//') + + # Parse version components + IFS='.' read -r major minor patch <<< "$base_version" + + if [ "$version_type" = "prerelease" ]; then + if [ -z "$prerelease_type" ]; then + echo "❌ Error: prerelease_type must be specified when version is 'prerelease'" + exit 1 + fi + + # Map prerelease type to Python suffix + case "$prerelease_type" in + alpha) suffix="a" ;; + beta) suffix="b" ;; + rc) suffix="rc" ;; + esac + + # Determine prerelease number + if [ -n "$prerelease_increment" ]; then + pre_num="$prerelease_increment" + else + # Check if current version is same type of prerelease, if so increment + if echo "$current_version" | grep -qE "${suffix}[0-9]+$"; then + current_pre_num=$(echo "$current_version" | sed -E "s/.*${suffix}([0-9]+)$/\1/") + pre_num=$((current_pre_num + 1)) + else + pre_num=1 + fi + fi + + new_version="${base_version}${suffix}${pre_num}" + is_prerelease="true" + else + # Standard version bump + case "$version_type" in + patch) + patch=$((patch + 1)) + ;; + minor) + minor=$((minor + 1)) + patch=0 + ;; + major) + major=$((major + 1)) + minor=0 + patch=0 + ;; + esac + new_version="${major}.${minor}.${patch}" + is_prerelease="false" + fi + + echo "version=$new_version" >> $GITHUB_OUTPUT + echo "is_prerelease=$is_prerelease" >> $GITHUB_OUTPUT + echo "New version: $new_version (prerelease: $is_prerelease)" + + - name: Check if tag already exists + run: | + if git rev-parse "v${{ steps.new-version.outputs.version }}" >/dev/null 2>&1; then + echo "❌ Error: Tag v${{ steps.new-version.outputs.version }} already exists" + exit 1 + fi + echo "✅ Tag v${{ steps.new-version.outputs.version }} does not exist" + + - name: Update version in pyproject.toml + run: | + poetry version ${{ steps.new-version.outputs.version }} + + - name: Update version in langfuse/version.py + run: | + new_version="${{ steps.new-version.outputs.version }}" + sed -i "s/__version__ = \".*\"/__version__ = \"$new_version\"/" langfuse/version.py + echo "Updated langfuse/version.py:" + cat langfuse/version.py + + - name: Verify version consistency + run: | + pyproject_version=$(poetry version -s) + file_version=$(grep -oP '__version__ = "\K[^"]+' langfuse/version.py) + + echo "pyproject.toml version: $pyproject_version" + echo "langfuse/version.py version: $file_version" + + if [ "$pyproject_version" != "$file_version" ]; then + echo "❌ Error: Version mismatch between pyproject.toml and langfuse/version.py" + exit 1 + fi + + if [ "$pyproject_version" != "${{ steps.new-version.outputs.version }}" ]; then + echo "❌ Error: Version in files doesn't match expected version" + exit 1 + fi + + echo "✅ Versions are consistent: $pyproject_version" + + - name: Build package + run: poetry build + + - name: Verify build artifacts + run: | + echo "Verifying build artifacts..." + + if [ ! -d "dist" ]; then + echo "❌ Error: dist directory not found" + exit 1 + fi + + wheel_count=$(ls dist/*.whl 2>/dev/null | wc -l) + sdist_count=$(ls dist/*.tar.gz 2>/dev/null | wc -l) + + if [ "$wheel_count" -eq 0 ]; then + echo "❌ Error: No wheel file found in dist/" + exit 1 + fi + + if [ "$sdist_count" -eq 0 ]; then + echo "❌ Error: No source distribution found in dist/" + exit 1 + fi + + echo "✅ Build artifacts:" + ls -lh dist/ + + # Verify the version in the built artifacts matches + expected_version="${{ steps.new-version.outputs.version }}" + wheel_file=$(ls dist/*.whl | head -1) + if ! echo "$wheel_file" | grep -q "$expected_version"; then + echo "❌ Error: Wheel filename doesn't contain expected version $expected_version" + echo "Wheel file: $wheel_file" + exit 1 + fi + echo "✅ Artifact version verified" + + - name: Commit version changes + run: | + git add pyproject.toml langfuse/version.py + git commit -m "chore: release v${{ steps.new-version.outputs.version }}" + + - name: Create and push tag + id: push-tag + run: | + git tag "v${{ steps.new-version.outputs.version }}" + git push origin main + git push origin "v${{ steps.new-version.outputs.version }}" + + - name: Publish to PyPI + id: publish-pypi + uses: pypa/gh-action-pypi-publish@release/v1 + with: + print-hash: true + + - name: Create GitHub Release + id: create-release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.new-version.outputs.version }} + name: v${{ steps.new-version.outputs.version }} + generate_release_notes: true + prerelease: ${{ steps.new-version.outputs.is_prerelease == 'true' }} + files: | + dist/*.whl + dist/*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} + + - name: Notify Slack on success + if: success() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "✅ Langfuse Python SDK v${{ steps.new-version.outputs.version }} published to PyPI", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "✅ Langfuse Python SDK Released", + "emoji": true + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Version:*\n`v${{ steps.new-version.outputs.version }}`" + }, + { + "type": "mrkdwn", + "text": "*Type:*\n`${{ inputs.version }}${{ inputs.prerelease_type && format(' ({0})', inputs.prerelease_type) || '' }}`" + }, + { + "type": "mrkdwn", + "text": "*Released by:*\n${{ github.actor }}" + }, + { + "type": "mrkdwn", + "text": "*Package:*\n`langfuse`" + } + ] + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "📋 View Release Notes", + "emoji": true + }, + "url": "${{ github.server_url }}/${{ github.repository }}/releases/tag/v${{ steps.new-version.outputs.version }}", + "style": "primary" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "📦 View on PyPI", + "emoji": true + }, + "url": "https://pypi.org/project/langfuse/${{ steps.new-version.outputs.version }}/" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "🔧 View Workflow", + "emoji": true + }, + "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + ] + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": "🔒 Published with Trusted Publishing (OIDC) • 🤖 Automated release" + } + ] + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_RELEASES }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + + - name: Notify Slack on failure + if: failure() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "❌ Langfuse Python SDK release workflow failed", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "❌ Langfuse Python SDK Release Failed", + "emoji": true + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "⚠️ The release workflow encountered an error and did not complete successfully." + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Requested Version:*\n`${{ inputs.version }}`" + }, + { + "type": "mrkdwn", + "text": "*Pre-release Type:*\n${{ inputs.prerelease_type || 'N/A' }}" + }, + { + "type": "mrkdwn", + "text": "*Triggered by:*\n${{ github.actor }}" + }, + { + "type": "mrkdwn", + "text": "*Current Version:*\n`${{ steps.current-version.outputs.version }}`" + } + ] + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*🔍 Troubleshooting:*\n• Check workflow logs for error details\n• Verify PyPI Trusted Publishing is configured correctly\n• Ensure the version doesn't already exist on PyPI\n• Check if the git tag already exists\n• If partially published, check PyPI and GitHub releases" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "🔧 View Workflow Logs", + "emoji": true + }, + "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "style": "danger" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "📖 PyPI Trusted Publishing Docs", + "emoji": true + }, + "url": "https://docs.pypi.org/trusted-publishers/" + } + ] + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": "🚨 Action required • Check workflow logs for details" + } + ] + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/CLAUDE.md b/CLAUDE.md index b0f36e8d6..01afe275d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,6 @@ This is the Langfuse Python SDK, a client library for accessing the Langfuse obs ```bash # Install Poetry plugins (one-time setup) poetry self add poetry-dotenv-plugin -poetry self add poetry-bumpversion # Install all dependencies including optional extras poetry install --all-extras @@ -50,16 +49,21 @@ poetry run pre-commit run --all-files ### Building and Releasing ```bash -# Build the package +# Build the package locally (for testing) poetry build -# Run release script (handles versioning, building, tagging, and publishing) -poetry run release - # Generate documentation poetry run pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse ``` +Releases are automated via GitHub Actions. To release: +1. Go to Actions > "Release Python SDK" workflow +2. Click "Run workflow" +3. Select version bump type (patch/minor/major/prerelease) +4. For prereleases, select the type (alpha/beta/rc) + +The workflow handles versioning, building, PyPI publishing (via OIDC), and GitHub release creation. + ## Architecture ### Core Components @@ -112,7 +116,7 @@ Environment variables (defined in `_client/environment_variables.py`): - `pyproject.toml`: Poetry configuration, dependencies, and tool settings - `ruff.toml`: Local development linting config (stricter) - `ci.ruff.toml`: CI linting config (more permissive) -- `langfuse/version.py`: Version string (updated by release script) +- `langfuse/version.py`: Version string (updated by CI release workflow) ## API Generation diff --git a/pyproject.toml b/pyproject.toml index de7caa2a3..9030c1de3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,8 +45,6 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] log_cli = true -[tool.poetry_bumpversion.file."langfuse/version.py"] - [tool.mypy] python_version = "3.12" warn_return_any = true @@ -103,5 +101,3 @@ module = [ ] ignore_errors = true -[tool.poetry.scripts] -release = "scripts.release:main" diff --git a/scripts/release.py b/scripts/release.py deleted file mode 100644 index 605d02dc9..000000000 --- a/scripts/release.py +++ /dev/null @@ -1,184 +0,0 @@ -"""@private""" - -import subprocess -import sys -import argparse -import logging -import re - -# Configure logging -logging.basicConfig(level=logging.INFO, format="🤖 Release SDK - %(message)s") - - -def run_command(command, check=True): - logging.info(f"Running command: {command}") - result = subprocess.run( - command, shell=True, check=check, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - return result.stdout.decode("utf-8").strip() - - -def check_git_status(): - # Check if there are uncommitted changes - logging.info("Checking for uncommitted changes...") - status_output = run_command("git status --porcelain", check=False) - if status_output: - logging.error( - "Your working directory has uncommitted changes. Please commit or stash them before proceeding." - ) - sys.exit(1) - - # Check if the current branch is 'main' - logging.info("Checking the current branch...") - current_branch = run_command("git rev-parse --abbrev-ref HEAD") - if current_branch != "main": - logging.error( - "You are not on the 'main' branch. Please switch to 'main' before proceeding." - ) - sys.exit(1) - - # Pull the latest changes from remote 'main' - logging.info("Pulling the latest changes from remote 'main'...") - run_command("git pull origin main") - - -def get_latest_tag(): - try: - latest_tag = run_command("git describe --tags --abbrev=0") - if latest_tag.startswith("v"): - latest_tag = latest_tag[1:] - except subprocess.CalledProcessError: - latest_tag = "0.0.0" # default if no tags exist - return latest_tag - - -def increment_version(current_version, increment_type): - major, minor, patch = map(int, current_version.split(".")) - if increment_type == "patch": - patch += 1 - elif increment_type == "minor": - minor += 1 - patch = 0 - elif increment_type == "major": - major += 1 - minor = 0 - patch = 0 - return f"{major}.{minor}.{patch}" - - -def update_version_file(version): - version_file_path = "langfuse/version.py" - logging.info(f"Updating version in {version_file_path} to {version}...") - - with open(version_file_path, "r") as file: - content = file.read() - - new_content = re.sub( - r'__version__ = "\d+\.\d+\.\d+"', f'__version__ = "{version}"', content - ) - - with open(version_file_path, "w") as file: - file.write(new_content) - - logging.info(f"Updated version in {version_file_path}.") - - -def main(): - parser = argparse.ArgumentParser( - description="Automate the release process for the Langfuse Python SDK using Poetry." - ) - parser.add_argument( - "increment_type", - choices=["patch", "minor", "major"], - help="Specify the type of version increment.", - ) - args = parser.parse_args() - - increment_type = args.increment_type - - try: - logging.info("Starting release process...") - - # Preliminary checks - logging.info("Performing preliminary checks...") - check_git_status() - logging.info("Git status is clean, on 'main' branch, and up to date.") - - # Get the latest tag - current_version = get_latest_tag() - logging.info(f"Current version: v{current_version}") - - # Determine the new version - new_version = increment_version(current_version, increment_type) - logging.info(f"Proposed new version: v{new_version}") - - # Ask for user confirmation - confirm = input( - f"Do you want to proceed with the release version v{new_version}? (y/n): " - ) - if confirm.lower() != "y": - logging.info("Release process aborted by user.") - sys.exit(0) - - # Step 1: Update the version - logging.info("Step 1: Updating the version...") - run_command(f"poetry version {new_version}") - logging.info(f"Updated to version v{new_version}") - - # Update the version in langfuse/version.py - update_version_file(new_version) - - # Ask for user confirmation - confirm = input( - f"Please check the changed files in the working tree. Proceed with releasing v{new_version}? (y/n): " - ) - if confirm.lower() != "y": - logging.info("Release process aborted by user.") - sys.exit(0) - - # Step 2: Install dependencies - logging.info("Step 2: Installing dependencies...") - run_command("poetry install") - - # Step 3: Build the package - logging.info("Step 3: Building the package...") - run_command("poetry build") - - # Step 4: Commit the changes - logging.info("Step 4: Committing the changes...") - run_command(f'git commit -am "chore: release v{new_version}"') - - # Step 5: Push the commit - logging.info("Step 5: Pushing the commit...") - run_command("git push") - - # Step 6: Tag the version - logging.info("Step 6: Tagging the version...") - run_command(f"git tag v{new_version}") - - # Step 7: Push the tags - logging.info("Step 7: Pushing the tags...") - run_command("git push --tags") - - # # Step 8: Publish to PyPi - logging.info("Step 8: Publishing to PyPi...") - run_command("poetry publish") - logging.info("Published to PyPi successfully.") - - # Step 9: Prompt the user to create a GitHub release - logging.info( - "Step 9: Please create a new release on GitHub by visiting the following URL:" - ) - print( - "Go to: https://github.com/langfuse/langfuse-python/releases to create the release." - ) - logging.info("Release process completed successfully.") - - except subprocess.CalledProcessError as e: - logging.error(f"An error occurred while running command: {e.cmd}") - logging.error(e.stderr.decode("utf-8")) - sys.exit(1) - - -if __name__ == "__main__": - main() From ceba1c2d7dec0097d2932d78b9130ac47411989d Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 3 Dec 2025 16:54:13 +0000 Subject: [PATCH 125/296] chore: release v3.10.4 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index f48b225b4..6592db4cc 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.3" +__version__ = "3.10.4" diff --git a/pyproject.toml b/pyproject.toml index 9030c1de3..c6121c30d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.3" +version = "3.10.4" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From c820ee83eb25452c2f3c28ebd54ba7f7ce2401c8 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 3 Dec 2025 16:59:07 +0000 Subject: [PATCH 126/296] chore: release v3.10.4a1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 6592db4cc..8741d448f 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.4" +__version__ = "3.10.4a1" diff --git a/pyproject.toml b/pyproject.toml index c6121c30d..6412d4ff3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.4" +version = "3.10.4a1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From be1c0abd33621cc491554bc8d7d4f652dbdb086f Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:03:34 +0100 Subject: [PATCH 127/296] docs: update CONTRIBUTING.md on release process --- CONTRIBUTING.md | 48 +++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d2bfd294..14a42a10d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,6 @@ ``` poetry self add poetry-dotenv-plugin -poetry self add poetry-bumpversion ``` ### Install dependencies @@ -58,34 +57,25 @@ poetry run mypy langfuse --no-error-summary ### Publish release -#### Release script - -Make sure you have your PyPi API token setup in your poetry config. If not, you can set it up by running: - -```sh -poetry config pypi-token.pypi $your-api-token -``` - -Run the release script: - -```sh -poetry run release -``` - -#### Manual steps (for prepatch versions) - -1. `poetry version patch` - - `poetry version prepatch` for pre-release versions -2. `poetry install` -3. `poetry build` -4. `git commit -am "chore: release v{version}"` -5. `git push` -6. `git tag v{version}` -7. `git push --tags` -8. `poetry publish` - - Create PyPi API token: - - Setup: `poetry config pypi-token.pypi your-api-token` -9. Create a release on GitHub with the changelog +Releases are automated via GitHub Actions using PyPI Trusted Publishing (OIDC). + +To create a release: + +1. Go to [Actions > Release Python SDK](https://github.com/langfuse/langfuse-python/actions/workflows/release.yml) +2. Click "Run workflow" +3. Select the version bump type: + - `patch` - Bug fixes (1.0.0 → 1.0.1) + - `minor` - New features (1.0.0 → 1.1.0) + - `major` - Breaking changes (1.0.0 → 2.0.0) + - `prerelease` - Pre-release versions (1.0.0 → 1.0.0a1) +4. For pre-releases, select the type: `alpha`, `beta`, or `rc` +5. Click "Run workflow" + +The workflow will automatically: +- Bump the version in `pyproject.toml` and `langfuse/version.py` +- Build the package +- Publish to PyPI +- Create a git tag and GitHub release with auto-generated release notes ### SDK Reference From 3e043be1410f882c053dfb52a87cd75012e94d74 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:31:52 +0100 Subject: [PATCH 128/296] feat(client): Add optional timestamp parameter to score creation (#1464) feat(client): Add optional timestamp parameter to score creation methods (#1463) * add optional param to configure timestamp when scoring * move inline imports to top * alphabetic sorting of imports in case that matters Co-authored-by: Conrad --- langfuse/_client/client.py | 6 +++- langfuse/_client/span.py | 10 +++++++ tests/test_core_sdk.py | 56 +++++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 46586f3b0..a3f653ada 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -1972,6 +1972,7 @@ def create_score( comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, + timestamp: Optional[datetime] = None, ) -> None: ... @overload @@ -1989,6 +1990,7 @@ def create_score( comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, + timestamp: Optional[datetime] = None, ) -> None: ... def create_score( @@ -2005,6 +2007,7 @@ def create_score( comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, + timestamp: Optional[datetime] = None, ) -> None: """Create a score for a specific trace or observation. @@ -2023,6 +2026,7 @@ def create_score( comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse metadata: Optional metadata to be attached to the score + timestamp: Optional timestamp for the score (defaults to current UTC time) Example: ```python @@ -2069,7 +2073,7 @@ def create_score( event = { "id": self.create_trace_id(), "type": "score-create", - "timestamp": _get_timestamp(), + "timestamp": timestamp or _get_timestamp(), "body": new_body, } diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 72ebb6bee..2f0000e41 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -276,6 +276,7 @@ def score( data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + timestamp: Optional[datetime] = None, ) -> None: ... @overload @@ -288,6 +289,7 @@ def score( data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, + timestamp: Optional[datetime] = None, ) -> None: ... def score( @@ -299,6 +301,7 @@ def score( data_type: Optional[ScoreDataType] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + timestamp: Optional[datetime] = None, ) -> None: """Create a score for this specific span. @@ -312,6 +315,7 @@ def score( data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse + timestamp: Optional timestamp for the score (defaults to current UTC time) Example: ```python @@ -337,6 +341,7 @@ def score( data_type=cast(Literal["CATEGORICAL"], data_type), comment=comment, config_id=config_id, + timestamp=timestamp, ) @overload @@ -349,6 +354,7 @@ def score_trace( data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + timestamp: Optional[datetime] = None, ) -> None: ... @overload @@ -361,6 +367,7 @@ def score_trace( data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, + timestamp: Optional[datetime] = None, ) -> None: ... def score_trace( @@ -372,6 +379,7 @@ def score_trace( data_type: Optional[ScoreDataType] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + timestamp: Optional[datetime] = None, ) -> None: """Create a score for the entire trace that this span belongs to. @@ -386,6 +394,7 @@ def score_trace( data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse + timestamp: Optional timestamp for the score (defaults to current UTC time) Example: ```python @@ -410,6 +419,7 @@ def score_trace( data_type=cast(Literal["CATEGORICAL"], data_type), comment=comment, config_id=config_id, + timestamp=timestamp, ) def _set_processed_span_attributes( diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 39c8de92d..b2661eef5 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1,7 +1,7 @@ import os import time from asyncio import gather -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from time import sleep import pytest @@ -257,6 +257,60 @@ def test_create_categorical_score(): assert trace["scores"][0]["stringValue"] == "high score" +def test_create_score_with_custom_timestamp(): + langfuse = Langfuse() + api_wrapper = LangfuseAPI() + + # Create a span and set trace properties + with langfuse.start_as_current_span(name="test-span") as span: + span.update_trace( + name="test-custom-timestamp", + user_id="test", + metadata="test", + ) + # Get trace ID for later use + trace_id = span.trace_id + + # Ensure data is sent + langfuse.flush() + sleep(2) + + custom_timestamp = datetime.now(timezone.utc) - timedelta(hours=1) + score_id = create_uuid() + langfuse.create_score( + score_id=score_id, + trace_id=trace_id, + name="custom-timestamp-score", + value=0.85, + data_type="NUMERIC", + timestamp=custom_timestamp, + ) + + # Ensure data is sent + langfuse.flush() + sleep(2) + + # Retrieve and verify + trace = api_wrapper.get_trace(trace_id) + + assert trace["scores"][0]["id"] == score_id + assert trace["scores"][0]["dataType"] == "NUMERIC" + assert trace["scores"][0]["value"] == 0.85 + + # Verify timestamp is close to our custom timestamp + # Parse the timestamp from the API response + response_timestamp = datetime.fromisoformat( + trace["scores"][0]["timestamp"].replace("Z", "+00:00") + ) + + # Check that the timestamps are within 1 second of each other + # (allowing for some processing time and rounding) + time_diff = abs((response_timestamp - custom_timestamp).total_seconds()) + assert time_diff < 1, ( + f"Timestamp difference too large: {time_diff}s. Expected < 1s. Custom: {custom_timestamp}, Response: {response_timestamp}" + ) + + def test_create_trace(): langfuse = Langfuse() trace_name = create_uuid() From 7e8f0707791f18c6f116420c6ca7f58db89452c8 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:48:34 +0100 Subject: [PATCH 129/296] fix(resource_manager): define tracer_provider if tracing_enabled=False (#1465) --- langfuse/_client/resource_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 23b6ee557..59fb54444 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -173,6 +173,7 @@ def _initialize_instance( self.sample_rate = sample_rate self.blocked_instrumentation_scopes = blocked_instrumentation_scopes self.additional_headers = additional_headers + self.tracer_provider = None # OTEL Tracer if tracing_enabled: From f3eedcac90ffddea9d382eeaee1f1fdf109e0f8c Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 3 Dec 2025 17:49:27 +0000 Subject: [PATCH 130/296] chore: release v3.10.5 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 8741d448f..215706496 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.4a1" +__version__ = "3.10.5" diff --git a/pyproject.toml b/pyproject.toml index 6412d4ff3..18d05bd93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.4a1" +version = "3.10.5" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From c048966a42c88571ddf3e50b75c2cd114e9f10e3 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 5 Dec 2025 10:12:17 +0100 Subject: [PATCH 131/296] feat(api): update API spec from langfuse/langfuse fc04a50 (#1467) Co-authored-by: langfuse-bot --- langfuse/api/reference.md | 93 ++++++++++++- langfuse/api/resources/projects/client.py | 4 +- langfuse/api/resources/prompts/client.py | 162 ++++++++++++++++++++++ 3 files changed, 256 insertions(+), 3 deletions(-) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 7c434f7d2..66c008bb7 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -4482,7 +4482,7 @@ client.organizations.get_organization_api_keys()
-Get Project associated with API key +Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key.
@@ -5461,6 +5461,97 @@ client.prompts.create(
+ + + + +
client.prompts.delete(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.prompts.delete( + prompt_name="promptName", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**prompt_name:** `str` — The name of the prompt + +
+
+ +
+
+ +**label:** `typing.Optional[str]` — Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + +
+
+ +
+
+ +**version:** `typing.Optional[int]` — Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
diff --git a/langfuse/api/resources/projects/client.py b/langfuse/api/resources/projects/client.py index 9af7cfdfa..5af232dfb 100644 --- a/langfuse/api/resources/projects/client.py +++ b/langfuse/api/resources/projects/client.py @@ -32,7 +32,7 @@ def get( self, *, request_options: typing.Optional[RequestOptions] = None ) -> Projects: """ - Get Project associated with API key + Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. Parameters ---------- @@ -545,7 +545,7 @@ async def get( self, *, request_options: typing.Optional[RequestOptions] = None ) -> Projects: """ - Get Project associated with API key + Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. Parameters ---------- diff --git a/langfuse/api/resources/prompts/client.py b/langfuse/api/resources/prompts/client.py index 793cf3f77..b8d6f31d4 100644 --- a/langfuse/api/resources/prompts/client.py +++ b/langfuse/api/resources/prompts/client.py @@ -292,6 +292,83 @@ def create( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + def delete( + self, + prompt_name: str, + *, + label: typing.Optional[str] = None, + version: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: + """ + Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. + + Parameters + ---------- + prompt_name : str + The name of the prompt + + label : typing.Optional[str] + Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + + version : typing.Optional[int] + Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.prompts.delete( + prompt_name="promptName", + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", + method="DELETE", + params={"label": label, "version": version}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + class AsyncPromptsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -585,3 +662,88 @@ async def main() -> None: except JSONDecodeError: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) + + async def delete( + self, + prompt_name: str, + *, + label: typing.Optional[str] = None, + version: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: + """ + Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. + + Parameters + ---------- + prompt_name : str + The name of the prompt + + label : typing.Optional[str] + Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + + version : typing.Optional[int] + Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.prompts.delete( + prompt_name="promptName", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", + method="DELETE", + params={"label": label, "version": version}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) From a00c020079fb3bd51dc8833dd5cee7de6f474fae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:24:36 +0100 Subject: [PATCH 132/296] chore(deps): bump urllib3 from 2.5.0 to 2.6.0 (#1468) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index a54e5fe7e..b569a2a93 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2450,21 +2450,21 @@ typing-extensions = ">=4.12.0" [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"}, + {file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" From 42d7e568192a6a64319441e395bdb400690c97cb Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:47:15 +0100 Subject: [PATCH 133/296] fix(openai): handle parsed_n non integer (#1470) --- langfuse/openai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index c9d277fa4..92d9346f4 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -492,7 +492,7 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A modelParameters.pop("max_tokens", None) modelParameters["max_completion_tokens"] = parsed_max_completion_tokens - if parsed_n is not None and parsed_n > 1: + if parsed_n is not None and isinstance(parsed_n, int) and parsed_n > 1: modelParameters["n"] = parsed_n if parsed_seed is not None: From 2900a43eee63f1116103fbd3640be574c622765c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:26:47 +0100 Subject: [PATCH 134/296] fix(resource-manager): flush custom tracer provider if provided (#1469) * fix(resource-manager): flush custom tracer provider if provided * push --- langfuse/_client/resource_manager.py | 16 +++++++++------- tests/test_propagate_attributes.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 59fb54444..aa9c7ce89 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -173,7 +173,7 @@ def _initialize_instance( self.sample_rate = sample_rate self.blocked_instrumentation_scopes = blocked_instrumentation_scopes self.additional_headers = additional_headers - self.tracer_provider = None + self.tracer_provider: Optional[TracerProvider] = None # OTEL Tracer if tracing_enabled: @@ -399,9 +399,10 @@ def _stop_and_join_consumer_threads(self) -> None: ) def flush(self) -> None: - tracer_provider = cast(TracerProvider, otel_trace_api.get_tracer_provider()) - if not isinstance(tracer_provider, otel_trace_api.ProxyTracerProvider): - tracer_provider.force_flush() + if self.tracer_provider is not None and not isinstance( + self.tracer_provider, otel_trace_api.ProxyTracerProvider + ): + self.tracer_provider.force_flush() langfuse_logger.debug("Successfully flushed OTEL tracer provider") self._score_ingestion_queue.join() @@ -414,9 +415,10 @@ def shutdown(self) -> None: # Unregister the atexit handler first atexit.unregister(self.shutdown) - tracer_provider = cast(TracerProvider, otel_trace_api.get_tracer_provider()) - if not isinstance(tracer_provider, otel_trace_api.ProxyTracerProvider): - tracer_provider.force_flush() + if self.tracer_provider is not None and not isinstance( + self.tracer_provider, otel_trace_api.ProxyTracerProvider + ): + self.tracer_provider.force_flush() self._stop_and_join_consumer_threads() diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py index affa84dd2..d724b6d65 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/test_propagate_attributes.py @@ -54,7 +54,7 @@ def get_span_by_name(self, memory_exporter, name: str) -> dict: AssertionError: If zero or more than one span with the name exists """ spans = self.get_spans_by_name(memory_exporter, name) - assert len(spans) == 1, f"Expected 1 span named '{name}', found {len(spans)}" + assert len(spans) > 0, f"Expected at least 1 span named '{name}'" return spans[0] def verify_missing_attribute(self, span_data: dict, attr_key: str): From a139e19b14ae4f7c6c4da94adce6d7a21e090485 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 12 Dec 2025 13:29:11 +0000 Subject: [PATCH 135/296] chore: release v3.10.6 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 215706496..65650dbda 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.5" +__version__ = "3.10.6" diff --git a/pyproject.toml b/pyproject.toml index 18d05bd93..23c3ad1bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.5" +version = "3.10.6" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From b72aaf02103acbc312569813eed893b2097265a8 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:13:50 +0100 Subject: [PATCH 136/296] fix(client): flush on shutdown (#1474) --- langfuse/_client/resource_manager.py | 6 +----- tests/test_datasets.py | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index aa9c7ce89..08c008234 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -415,11 +415,7 @@ def shutdown(self) -> None: # Unregister the atexit handler first atexit.unregister(self.shutdown) - if self.tracer_provider is not None and not isinstance( - self.tracer_provider, otel_trace_api.ProxyTracerProvider - ): - self.tracer_provider.force_flush() - + self.flush() self._stop_and_join_consumer_threads() diff --git a/tests/test_datasets.py b/tests/test_datasets.py index c3ad7a318..c1b81868d 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -50,6 +50,7 @@ def test_create_dataset_item(): source_trace_id=generation.trace_id, ) langfuse.create_dataset_item( + input="Hello", dataset_name=name, ) @@ -67,7 +68,7 @@ def test_create_dataset_item(): assert dataset.items[1].source_trace_id == generation.trace_id assert dataset.items[1].dataset_name == name - assert dataset.items[0].input is None + assert dataset.items[0].input == "Hello" assert dataset.items[0].expected_output is None assert dataset.items[0].metadata is None assert dataset.items[0].source_observation_id is None From 9afb9a8c1c5cd6eb02c553003210d4a6450ece56 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 16 Dec 2025 15:36:42 +0000 Subject: [PATCH 137/296] chore: release v3.10.7 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 65650dbda..2e0d5057a 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.6" +__version__ = "3.10.7" diff --git a/pyproject.toml b/pyproject.toml index 23c3ad1bd..acc9f7217 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.6" +version = "3.10.7" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 831270d6823c293510b36b2cd004e18f3addd7e3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:11:02 +0100 Subject: [PATCH 138/296] Langchain callback handler docs (#1476) feat: Add trace_context to LangchainCallbackHandler Co-authored-by: Cursor Agent --- langfuse/langchain/CallbackHandler.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 7e23b155a..7f6479867 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -104,6 +104,19 @@ def __init__( Args: public_key: Optional Langfuse public key. If not provided, will use the default client configuration. update_trace: Whether to update the Langfuse trace with the chains input / output / metadata / name. Defaults to False. + trace_context: Optional context for connecting to an existing trace (distributed tracing) or + setting a custom trace id for the root LangChain run. Pass a `TraceContext` dict, e.g. + `{"trace_id": ""}` (and optionally `{"parent_span_id": ""}`) to link + the trace to an upstream system. + + Example: + Use a custom trace id without context managers: + + ```python + from langfuse.langchain import CallbackHandler + + handler = CallbackHandler(trace_context={"trace_id": "my-trace-id"}) + ``` """ self.client = get_client(public_key=public_key) self.run_inline = True From 9d67ded983fe0322c34e72a6aa4f77ac8241703f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 23:04:39 +0000 Subject: [PATCH 139/296] chore(deps-dev): bump filelock from 3.20.0 to 3.20.1 (#1477) Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.20.0 to 3.20.1. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.20.0...3.20.1) --- updated-dependencies: - dependency-name: filelock dependency-version: 3.20.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index b569a2a93..1ff0fca7f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -326,14 +326,14 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.20.0" +version = "3.20.1" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, - {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, + {file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"}, + {file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"}, ] [[package]] From 5d3a72887410a98a7853405899e82ab0c553ac6f Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:19:04 +0100 Subject: [PATCH 140/296] feat(client): allow propagating trace name (#1478) --- langfuse/_client/propagation.py | 9 ++ tests/test_propagate_attributes.py | 225 +++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 49d34a99f..0baca7e87 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -32,6 +32,7 @@ "metadata", "version", "tags", + "trace_name", ] InternalPropagatedKeys = Literal[ @@ -50,6 +51,7 @@ "metadata", "version", "tags", + "trace_name", "experiment_id", "experiment_name", "experiment_metadata", @@ -77,6 +79,7 @@ def propagate_attributes( metadata: Optional[Dict[str, str]] = None, version: Optional[str] = None, tags: Optional[List[str]] = None, + trace_name: Optional[str] = None, as_baggage: bool = False, ) -> _AgnosticContextManager[Any]: """Propagate trace-level attributes to all spans created within this context. @@ -109,6 +112,8 @@ def propagate_attributes( - AVOID: large payloads, sensitive data, non-string values (will be dropped with warning) version: Version identfier for parts of your application that are independently versioned, e.g. agents tags: List of tags to categorize the group of observations + trace_name: Name to assign to the trace. Must be US-ASCII string, ≤200 characters. + Use this to set a consistent trace name for all spans created within this context. as_baggage: If True, propagates attributes using OpenTelemetry baggage for cross-process/service propagation. **Security warning**: When enabled, attribute values are added to HTTP headers on ALL outbound requests. @@ -195,6 +200,7 @@ def propagate_attributes( metadata=metadata, version=version, tags=tags, + trace_name=trace_name, as_baggage=as_baggage, ) @@ -207,6 +213,7 @@ def _propagate_attributes( metadata: Optional[Dict[str, str]] = None, version: Optional[str] = None, tags: Optional[List[str]] = None, + trace_name: Optional[str] = None, as_baggage: bool = False, experiment: Optional[PropagatedExperimentAttributes] = None, ) -> Generator[Any, Any, Any]: @@ -218,6 +225,7 @@ def _propagate_attributes( "session_id": session_id, "version": version, "tags": tags, + "trace_name": trace_name, } propagated_string_attributes = propagated_string_attributes | ( @@ -456,6 +464,7 @@ def _get_propagated_span_key(key: str) -> str: "user_id": LangfuseOtelSpanAttributes.TRACE_USER_ID, "version": LangfuseOtelSpanAttributes.VERSION, "tags": LangfuseOtelSpanAttributes.TRACE_TAGS, + "trace_name": LangfuseOtelSpanAttributes.TRACE_NAME, "experiment_id": LangfuseOtelSpanAttributes.EXPERIMENT_ID, "experiment_name": LangfuseOtelSpanAttributes.EXPERIMENT_NAME, "experiment_metadata": LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py index d724b6d65..83c88e48a 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/test_propagate_attributes.py @@ -2769,3 +2769,228 @@ def task_with_child(*, item, **kwargs): LangfuseOtelSpanAttributes.ENVIRONMENT, LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, ) + + +class TestPropagateAttributesTraceName(TestPropagateAttributesBase): + """Tests for trace_name parameter propagation.""" + + def test_trace_name_propagates_to_child_spans( + self, langfuse_client, memory_exporter + ): + """Verify trace_name propagates to all child spans within context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name="my-trace-name"): + child1 = langfuse_client.start_span(name="child-span-1") + child1.end() + + child2 = langfuse_client.start_span(name="child-span-2") + child2.end() + + # Verify both children have trace_name + child1_span = self.get_span_by_name(memory_exporter, "child-span-1") + self.verify_span_attribute( + child1_span, + LangfuseOtelSpanAttributes.TRACE_NAME, + "my-trace-name", + ) + + child2_span = self.get_span_by_name(memory_exporter, "child-span-2") + self.verify_span_attribute( + child2_span, + LangfuseOtelSpanAttributes.TRACE_NAME, + "my-trace-name", + ) + + def test_trace_name_propagates_to_grandchildren( + self, langfuse_client, memory_exporter + ): + """Verify trace_name propagates through multiple levels of nesting.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name="nested-trace"): + with langfuse_client.start_as_current_span(name="child-span"): + grandchild = langfuse_client.start_span(name="grandchild-span") + grandchild.end() + + # Verify all three levels have trace_name + parent_span = self.get_span_by_name(memory_exporter, "parent-span") + child_span = self.get_span_by_name(memory_exporter, "child-span") + grandchild_span = self.get_span_by_name(memory_exporter, "grandchild-span") + + for span in [parent_span, child_span, grandchild_span]: + self.verify_span_attribute( + span, LangfuseOtelSpanAttributes.TRACE_NAME, "nested-trace" + ) + + def test_trace_name_with_user_and_session(self, langfuse_client, memory_exporter): + """Verify trace_name works together with user_id and session_id.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + user_id="user_123", + session_id="session_abc", + trace_name="combined-trace", + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has all attributes + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_abc" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "combined-trace" + ) + + def test_trace_name_with_version(self, langfuse_client, memory_exporter): + """Verify trace_name works together with version.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + trace_name="versioned-trace", + version="1.0.0", + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "versioned-trace" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.VERSION, "1.0.0" + ) + + def test_trace_name_with_metadata(self, langfuse_client, memory_exporter): + """Verify trace_name works together with metadata.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + trace_name="metadata-trace", + metadata={"env": "production", "region": "us-east"}, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "metadata-trace" + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env", + "production", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region", + "us-east", + ) + + def test_trace_name_validation_over_200_chars( + self, langfuse_client, memory_exporter + ): + """Verify trace_name over 200 characters is dropped with warning.""" + long_name = "trace-" + "a" * 200 # Create a very long trace name + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name=long_name): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have trace_name + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.TRACE_NAME) + + def test_trace_name_exactly_200_chars(self, langfuse_client, memory_exporter): + """Verify exactly 200 character trace_name is accepted.""" + trace_name_200 = "t" * 200 + + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name=trace_name_200): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child HAS trace_name + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_NAME, trace_name_200 + ) + + def test_trace_name_nested_contexts_inner_overwrites( + self, langfuse_client, memory_exporter + ): + """Verify inner context overwrites outer trace_name.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name="outer-trace"): + # Create span in outer context + span1 = langfuse_client.start_span(name="span-1") + span1.end() + + # Inner context with different trace_name + with propagate_attributes(trace_name="inner-trace"): + span2 = langfuse_client.start_span(name="span-2") + span2.end() + + # Back to outer context + span3 = langfuse_client.start_span(name="span-3") + span3.end() + + # Verify: span1 and span3 have outer-trace, span2 has inner-trace + span1_data = self.get_span_by_name(memory_exporter, "span-1") + self.verify_span_attribute( + span1_data, LangfuseOtelSpanAttributes.TRACE_NAME, "outer-trace" + ) + + span2_data = self.get_span_by_name(memory_exporter, "span-2") + self.verify_span_attribute( + span2_data, LangfuseOtelSpanAttributes.TRACE_NAME, "inner-trace" + ) + + span3_data = self.get_span_by_name(memory_exporter, "span-3") + self.verify_span_attribute( + span3_data, LangfuseOtelSpanAttributes.TRACE_NAME, "outer-trace" + ) + + def test_trace_name_sets_on_current_span(self, langfuse_client, memory_exporter): + """Verify trace_name is set on the current span when entering context.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name="current-trace"): + pass # Just enter and exit context + + # Verify parent span has trace_name set + parent_span = self.get_span_by_name(memory_exporter, "parent-span") + self.verify_span_attribute( + parent_span, LangfuseOtelSpanAttributes.TRACE_NAME, "current-trace" + ) + + def test_trace_name_non_string_dropped(self, langfuse_client, memory_exporter): + """Verify non-string trace_name is dropped with warning.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes(trace_name=123): # type: ignore + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child does NOT have trace_name + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.TRACE_NAME) + + def test_trace_name_with_baggage(self, langfuse_client, memory_exporter): + """Verify trace_name propagates through baggage.""" + with langfuse_client.start_as_current_span(name="parent-span"): + with propagate_attributes( + trace_name="baggage-trace", + user_id="user_123", + as_baggage=True, + ): + child = langfuse_client.start_span(name="child-span") + child.end() + + # Verify child has trace_name + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "baggage-trace" + ) + self.verify_span_attribute( + child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123" + ) From 9775c450009382f3deec0794ab87bc9db97d452b Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 17 Dec 2025 10:29:15 +0000 Subject: [PATCH 141/296] chore: release v3.11.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 2e0d5057a..bd30a827f 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.10.7" +__version__ = "3.11.0" diff --git a/pyproject.toml b/pyproject.toml index acc9f7217..97866a2bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.10.7" +version = "3.11.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 993d993cfd1905fddc49f2801a4c2699cd33ea42 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:23:51 +0100 Subject: [PATCH 142/296] chore: add release safeguard for major releases --- .github/workflows/release.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 758b546cc..551f6fad6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,9 +22,13 @@ on: - beta - rc prerelease_increment: - description: 'Pre-release number (e.g., 1 for alpha1). Leave empty to auto-increment or start at 1.' + description: "Pre-release number (e.g., 1 for alpha1). Leave empty to auto-increment or start at 1." type: string default: "" + confirm_major: + description: "Type RELEASE MAJOR to confirm a major release" + required: false + default: "" permissions: contents: write @@ -47,6 +51,14 @@ jobs: exit 1 fi + - name: Confirm major release + if: ${{ inputs.version == 'major' && inputs.dry_run == false }} + run: | + if [ "${{ inputs.confirm_major }}" != "RELEASE MAJOR" ]; then + echo "❌ For major releases, set confirm_major to RELEASE MAJOR" + exit 1 + fi + - name: Checkout repository uses: actions/checkout@v4 with: From 32dddd50d2498aaf80168ad562179c4178c4d93c Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 18 Dec 2025 10:27:41 +0100 Subject: [PATCH 143/296] feat(api): update API spec from langfuse/langfuse 3dbec46 (#1479) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 10 + langfuse/api/client.py | 13 + langfuse/api/reference.md | 538 +++++++++++++++++ langfuse/api/resources/__init__.py | 9 + .../api/resources/metrics_v_2/__init__.py | 5 + langfuse/api/resources/metrics_v_2/client.py | 461 +++++++++++++++ .../resources/metrics_v_2/types/__init__.py | 5 + .../metrics_v_2/types/metrics_v_2_response.py | 47 ++ .../resources/observations_v_2/__init__.py | 5 + .../api/resources/observations_v_2/client.py | 558 ++++++++++++++++++ .../observations_v_2/types/__init__.py | 6 + .../types/observations_v_2_meta.py | 49 ++ .../types/observations_v_2_response.py | 55 ++ 13 files changed, 1761 insertions(+) create mode 100644 langfuse/api/resources/metrics_v_2/__init__.py create mode 100644 langfuse/api/resources/metrics_v_2/client.py create mode 100644 langfuse/api/resources/metrics_v_2/types/__init__.py create mode 100644 langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py create mode 100644 langfuse/api/resources/observations_v_2/__init__.py create mode 100644 langfuse/api/resources/observations_v_2/client.py create mode 100644 langfuse/api/resources/observations_v_2/types/__init__.py create mode 100644 langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py create mode 100644 langfuse/api/resources/observations_v_2/types/observations_v_2_response.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index c958219d5..9ec280087 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -117,6 +117,7 @@ MembershipsResponse, MethodNotAllowedError, MetricsResponse, + MetricsV2Response, Model, ModelPrice, ModelUsageUnit, @@ -128,6 +129,8 @@ ObservationLevel, ObservationType, Observations, + ObservationsV2Meta, + ObservationsV2Response, ObservationsView, ObservationsViews, OpenAiCompletionUsageSchema, @@ -234,8 +237,10 @@ llm_connections, media, metrics, + metrics_v_2, models, observations, + observations_v_2, opentelemetry, organizations, projects, @@ -367,6 +372,7 @@ "MembershipsResponse", "MethodNotAllowedError", "MetricsResponse", + "MetricsV2Response", "Model", "ModelPrice", "ModelUsageUnit", @@ -378,6 +384,8 @@ "ObservationLevel", "ObservationType", "Observations", + "ObservationsV2Meta", + "ObservationsV2Response", "ObservationsView", "ObservationsViews", "OpenAiCompletionUsageSchema", @@ -484,8 +492,10 @@ "llm_connections", "media", "metrics", + "metrics_v_2", "models", "observations", + "observations_v_2", "opentelemetry", "organizations", "projects", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 646279b5a..09674e979 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -28,8 +28,13 @@ ) from .resources.media.client import AsyncMediaClient, MediaClient from .resources.metrics.client import AsyncMetricsClient, MetricsClient +from .resources.metrics_v_2.client import AsyncMetricsV2Client, MetricsV2Client from .resources.models.client import AsyncModelsClient, ModelsClient from .resources.observations.client import AsyncObservationsClient, ObservationsClient +from .resources.observations_v_2.client import ( + AsyncObservationsV2Client, + ObservationsV2Client, +) from .resources.opentelemetry.client import ( AsyncOpentelemetryClient, OpentelemetryClient, @@ -137,8 +142,12 @@ def __init__( self.ingestion = IngestionClient(client_wrapper=self._client_wrapper) self.llm_connections = LlmConnectionsClient(client_wrapper=self._client_wrapper) self.media = MediaClient(client_wrapper=self._client_wrapper) + self.metrics_v_2 = MetricsV2Client(client_wrapper=self._client_wrapper) self.metrics = MetricsClient(client_wrapper=self._client_wrapper) self.models = ModelsClient(client_wrapper=self._client_wrapper) + self.observations_v_2 = ObservationsV2Client( + client_wrapper=self._client_wrapper + ) self.observations = ObservationsClient(client_wrapper=self._client_wrapper) self.opentelemetry = OpentelemetryClient(client_wrapper=self._client_wrapper) self.organizations = OrganizationsClient(client_wrapper=self._client_wrapper) @@ -242,8 +251,12 @@ def __init__( client_wrapper=self._client_wrapper ) self.media = AsyncMediaClient(client_wrapper=self._client_wrapper) + self.metrics_v_2 = AsyncMetricsV2Client(client_wrapper=self._client_wrapper) self.metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) self.models = AsyncModelsClient(client_wrapper=self._client_wrapper) + self.observations_v_2 = AsyncObservationsV2Client( + client_wrapper=self._client_wrapper + ) self.observations = AsyncObservationsClient(client_wrapper=self._client_wrapper) self.opentelemetry = AsyncOpentelemetryClient( client_wrapper=self._client_wrapper diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 66c008bb7..b84696be4 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -2946,6 +2946,232 @@ client.media.get_upload_url( + + + + +## MetricsV2 +
client.metrics_v_2.metrics(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. + +## V2 Differences +- Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) +- Direct access to tags and release fields on observations +- Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view +- High cardinality dimensions are not supported and will return a 400 error (see below) + +For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + +## Available Views + +### observations +Query observation-level data (spans, generations, events). + +**Dimensions:** +- `environment` - Deployment environment (e.g., production, staging) +- `type` - Type of observation (SPAN, GENERATION, EVENT) +- `name` - Name of the observation +- `level` - Logging level of the observation +- `version` - Version of the observation +- `tags` - User-defined tags +- `release` - Release version +- `traceName` - Name of the parent trace (backwards-compatible) +- `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) +- `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) +- `providedModelName` - Name of the model used +- `promptName` - Name of the prompt used +- `promptVersion` - Version of the prompt used +- `startTimeMonth` - Month of start_time in YYYY-MM format + +**Measures:** +- `count` - Total number of observations +- `latency` - Observation latency (milliseconds) +- `streamingLatency` - Generation latency from completion start to end (milliseconds) +- `inputTokens` - Sum of input tokens consumed +- `outputTokens` - Sum of output tokens produced +- `totalTokens` - Sum of all tokens consumed +- `outputTokensPerSecond` - Output tokens per second +- `tokensPerSecond` - Total tokens per second +- `inputCost` - Input cost (USD) +- `outputCost` - Output cost (USD) +- `totalCost` - Total cost (USD) +- `timeToFirstToken` - Time to first token (milliseconds) +- `countScores` - Number of scores attached to the observation + +### scores-numeric +Query numeric and boolean score data. + +**Dimensions:** +- `environment` - Deployment environment +- `name` - Name of the score (e.g., accuracy, toxicity) +- `source` - Origin of the score (API, ANNOTATION, EVAL) +- `dataType` - Data type (NUMERIC, BOOLEAN) +- `configId` - Identifier of the score config +- `timestampMonth` - Month in YYYY-MM format +- `timestampDay` - Day in YYYY-MM-DD format +- `value` - Numeric value of the score +- `traceName` - Name of the parent trace +- `tags` - Tags +- `traceRelease` - Release version +- `traceVersion` - Version +- `observationName` - Name of the associated observation +- `observationModelName` - Model name of the associated observation +- `observationPromptName` - Prompt name of the associated observation +- `observationPromptVersion` - Prompt version of the associated observation + +**Measures:** +- `count` - Total number of scores +- `value` - Score value (for aggregations) + +### scores-categorical +Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + +**Measures:** +- `count` - Total number of scores + +## High Cardinality Dimensions +The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. +Use them in filters instead. + +**observations view:** +- `id` - Use traceId filter to narrow down results +- `traceId` - Use traceId filter instead +- `userId` - Use userId filter instead +- `sessionId` - Use sessionId filter instead +- `parentObservationId` - Use parentObservationId filter instead + +**scores-numeric / scores-categorical views:** +- `id` - Use specific filters to narrow down results +- `traceId` - Use traceId filter instead +- `userId` - Use userId filter instead +- `sessionId` - Use sessionId filter instead +- `observationId` - Use observationId filter instead + +## Aggregations +Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + +## Time Granularities +Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` +- `auto` bins the data into approximately 50 buckets based on the time range +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.metrics_v_2.metrics( + query="query", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**query:** `str` + +JSON string containing the query parameters with the following structure: +```json +{ + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by (see available dimensions above) + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Value to compare against + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by (dimension or metric alias) + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 + } +} +``` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -3377,6 +3603,318 @@ client.models.delete( + + + + +## ObservationsV2 +
client.observations_v_2.get_many(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get a list of observations with cursor-based pagination and flexible field selection. + +## Cursor-based Pagination +This endpoint uses cursor-based pagination for efficient traversal of large datasets. +The cursor is returned in the response metadata and should be passed in subsequent requests +to retrieve the next page of results. + +## Field Selection +Use the `fields` parameter to control which observation fields are returned: +- `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type +- `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId +- `time` - completionStartTime, createdAt, updatedAt +- `io` - input, output +- `metadata` - metadata +- `model` - providedModelName, internalModelId, modelParameters +- `usage` - usageDetails, costDetails, totalCost +- `prompt` - promptId, promptName, promptVersion +- `metrics` - latency, timeToFirstToken + +If not specified, `core` and `basic` field groups are returned. + +## Filters +Multiple filtering options are available via query parameters or the structured `filter` parameter. +When using the `filter` parameter, it takes precedence over individual query parameter filters. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from langfuse.client import FernLangfuse + +client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", +) +client.observations_v_2.get_many() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**fields:** `typing.Optional[str]` + +Comma-separated list of field groups to include in the response. +Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. +If not specified, `core` and `basic` field groups are returned. +Example: "basic,usage,model" + +
+
+ +
+
+ +**limit:** `typing.Optional[int]` — Number of items to return per page. Maximum 1000, default 50. + +
+
+ +
+
+ +**cursor:** `typing.Optional[str]` — Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + +
+
+ +
+
+ +**parse_io_as_json:** `typing.Optional[bool]` + +Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. +Defaults to `false` if not provided. + +
+
+ +
+
+ +**name:** `typing.Optional[str]` + +
+
+ +
+
+ +**user_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**type:** `typing.Optional[str]` — Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") + +
+
+ +
+
+ +**trace_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**level:** `typing.Optional[ObservationLevel]` — Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + +
+
+ +
+
+ +**parent_observation_id:** `typing.Optional[str]` + +
+
+ +
+
+ +**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for observations where the environment is one of the provided values. + +
+
+ +
+
+ +**from_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time on or after this datetime (ISO 8601). + +
+
+ +
+
+ +**to_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time before this datetime (ISO 8601). + +
+
+ +
+
+ +**version:** `typing.Optional[str]` — Optional filter to only include observations with a certain version. + +
+
+ +
+
+ +**filter:** `typing.Optional[str]` + +JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + +## Filter Structure +Each filter condition has the following structure: +```json +[ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } +] +``` + +## Available Columns + +### Core Observation Fields +- `id` (string) - Observation ID +- `type` (string) - Observation type (SPAN, GENERATION, EVENT) +- `name` (string) - Observation name +- `traceId` (string) - Associated trace ID +- `startTime` (datetime) - Observation start time +- `endTime` (datetime) - Observation end time +- `environment` (string) - Environment tag +- `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) +- `statusMessage` (string) - Status message +- `version` (string) - Version tag +- `userId` (string) - User ID +- `sessionId` (string) - Session ID + +### Trace-Related Fields +- `traceName` (string) - Name of the parent trace +- `traceTags` (arrayOptions) - Tags from the parent trace +- `tags` (arrayOptions) - Alias for traceTags + +### Performance Metrics +- `latency` (number) - Latency in seconds (calculated: end_time - start_time) +- `timeToFirstToken` (number) - Time to first token in seconds +- `tokensPerSecond` (number) - Output tokens per second + +### Token Usage +- `inputTokens` (number) - Number of input tokens +- `outputTokens` (number) - Number of output tokens +- `totalTokens` (number) - Total tokens (alias: `tokens`) + +### Cost Metrics +- `inputCost` (number) - Input cost in USD +- `outputCost` (number) - Output cost in USD +- `totalCost` (number) - Total cost in USD + +### Model Information +- `model` (string) - Provided model name (alias: `providedModelName`) +- `promptName` (string) - Associated prompt name +- `promptVersion` (number) - Associated prompt version + +### Structured Data +- `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + +## Filter Examples +```json +[ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } +] +``` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index d91362a28..1095119a7 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -13,8 +13,10 @@ llm_connections, media, metrics, + metrics_v_2, models, observations, + observations_v_2, opentelemetry, organizations, projects, @@ -178,8 +180,10 @@ PatchMediaBody, ) from .metrics import MetricsResponse +from .metrics_v_2 import MetricsV2Response from .models import CreateModelRequest, PaginatedModels from .observations import Observations, ObservationsViews +from .observations_v_2 import ObservationsV2Meta, ObservationsV2Response from .opentelemetry import ( OtelAttribute, OtelAttributeValue, @@ -388,6 +392,7 @@ "MembershipsResponse", "MethodNotAllowedError", "MetricsResponse", + "MetricsV2Response", "Model", "ModelPrice", "ModelUsageUnit", @@ -399,6 +404,8 @@ "ObservationLevel", "ObservationType", "Observations", + "ObservationsV2Meta", + "ObservationsV2Response", "ObservationsView", "ObservationsViews", "OpenAiCompletionUsageSchema", @@ -505,8 +512,10 @@ "llm_connections", "media", "metrics", + "metrics_v_2", "models", "observations", + "observations_v_2", "opentelemetry", "organizations", "projects", diff --git a/langfuse/api/resources/metrics_v_2/__init__.py b/langfuse/api/resources/metrics_v_2/__init__.py new file mode 100644 index 000000000..a8c9304a6 --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import MetricsV2Response + +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/client.py b/langfuse/api/resources/metrics_v_2/client.py new file mode 100644 index 000000000..4628c4d61 --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/client.py @@ -0,0 +1,461 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from .types.metrics_v_2_response import MetricsV2Response + + +class MetricsV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> MetricsV2Response: + """ + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. + + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by (see available dimensions above) + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Value to compare against + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by (dimension or metric alias) + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MetricsV2Response + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.metrics_v_2.metrics( + query="query", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/metrics", + method="GET", + params={"query": query}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(MetricsV2Response, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncMetricsV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> MetricsV2Response: + """ + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. + + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by (see available dimensions above) + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Value to compare against + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by (dimension or metric alias) + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MetricsV2Response + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.metrics_v_2.metrics( + query="query", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/metrics", + method="GET", + params={"query": query}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as(MetricsV2Response, _response.json()) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/metrics_v_2/types/__init__.py b/langfuse/api/resources/metrics_v_2/types/__init__.py new file mode 100644 index 000000000..b77cf3d4d --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/types/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .metrics_v_2_response import MetricsV2Response + +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py b/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py new file mode 100644 index 000000000..ff0a475ea --- /dev/null +++ b/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class MetricsV2Response(pydantic_v1.BaseModel): + data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() + """ + The metrics data. Each item in the list contains the metric values and dimensions requested in the query. + Format varies based on the query parameters. + Histograms will return an array with [lower, upper, height] tuples. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations_v_2/__init__.py b/langfuse/api/resources/observations_v_2/__init__.py new file mode 100644 index 000000000..a04697f31 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import ObservationsV2Meta, ObservationsV2Response + +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/resources/observations_v_2/client.py b/langfuse/api/resources/observations_v_2/client.py new file mode 100644 index 000000000..e6796fd46 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/client.py @@ -0,0 +1,558 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.datetime_utils import serialize_datetime +from ...core.pydantic_utilities import pydantic_v1 +from ...core.request_options import RequestOptions +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.observation_level import ObservationLevel +from .types.observations_v_2_response import ObservationsV2Response + + +class ObservationsV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_many( + self, + *, + fields: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, + name: typing.Optional[str] = None, + user_id: typing.Optional[str] = None, + type: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsV2Response: + """ + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. + + Parameters + ---------- + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + limit : typing.Optional[int] + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. + Defaults to `false` if not provided. + + name : typing.Optional[str] + + user_id : typing.Optional[str] + + type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") + + trace_id : typing.Optional[str] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name (alias: `providedModelName`) + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsV2Response + + Examples + -------- + from langfuse.client import FernLangfuse + + client = FernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.observations_v_2.get_many() + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/observations", + method="GET", + params={ + "fields": fields, + "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, + "name": name, + "userId": user_id, + "type": type, + "traceId": trace_id, + "level": level, + "parentObservationId": parent_observation_id, + "environment": environment, + "fromStartTime": serialize_datetime(from_start_time) + if from_start_time is not None + else None, + "toStartTime": serialize_datetime(to_start_time) + if to_start_time is not None + else None, + "version": version, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + ObservationsV2Response, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncObservationsV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_many( + self, + *, + fields: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, + name: typing.Optional[str] = None, + user_id: typing.Optional[str] = None, + type: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsV2Response: + """ + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. + + Parameters + ---------- + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + limit : typing.Optional[int] + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. + Defaults to `false` if not provided. + + name : typing.Optional[str] + + user_id : typing.Optional[str] + + type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") + + trace_id : typing.Optional[str] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name (alias: `providedModelName`) + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsV2Response + + Examples + -------- + import asyncio + + from langfuse.client import AsyncFernLangfuse + + client = AsyncFernLangfuse( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.observations_v_2.get_many() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/observations", + method="GET", + params={ + "fields": fields, + "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, + "name": name, + "userId": user_id, + "type": type, + "traceId": trace_id, + "level": level, + "parentObservationId": parent_observation_id, + "environment": environment, + "fromStartTime": serialize_datetime(from_start_time) + if from_start_time is not None + else None, + "toStartTime": serialize_datetime(to_start_time) + if to_start_time is not None + else None, + "version": version, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return pydantic_v1.parse_obj_as( + ObservationsV2Response, _response.json() + ) # type: ignore + if _response.status_code == 400: + raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + if _response.status_code == 401: + raise UnauthorizedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 403: + raise AccessDeniedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 405: + raise MethodNotAllowedError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + if _response.status_code == 404: + raise NotFoundError( + pydantic_v1.parse_obj_as(typing.Any, _response.json()) + ) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/observations_v_2/types/__init__.py b/langfuse/api/resources/observations_v_2/types/__init__.py new file mode 100644 index 000000000..b62504c61 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/types/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .observations_v_2_meta import ObservationsV2Meta +from .observations_v_2_response import ObservationsV2Response + +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py b/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py new file mode 100644 index 000000000..d720db59b --- /dev/null +++ b/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class ObservationsV2Meta(pydantic_v1.BaseModel): + """ + Metadata for cursor-based pagination + """ + + cursor: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Base64-encoded cursor to use for retrieving the next page. If not present, there are no more results. + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py b/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py new file mode 100644 index 000000000..fdea2c3c3 --- /dev/null +++ b/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .observations_v_2_meta import ObservationsV2Meta + + +class ObservationsV2Response(pydantic_v1.BaseModel): + """ + Response containing observations with field-group-based filtering and cursor-based pagination. + + The `data` array contains observation objects with only the requested field groups included. + Use the `cursor` in `meta` to retrieve the next page of results. + """ + + data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() + """ + Array of observation objects. Fields included depend on the `fields` parameter in the request. + """ + + meta: ObservationsV2Meta + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} From c700431185b4d8861a9c23beab480704fa6e8c5a Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:30:13 +0100 Subject: [PATCH 144/296] fix(langchain): allow prompt linking with langchain v1 create_agent (#1481) * fix(langchain): allow prompt linking with langchain v1 create_agent * push --- langfuse/langchain/CallbackHandler.py | 56 ++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 7f6479867..a2e9816da 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -28,10 +28,10 @@ LangfuseSpan, LangfuseTool, ) -from langfuse.types import TraceContext from langfuse._utils import _get_timestamp from langfuse.langchain.utils import _extract_model_name from langfuse.logger import langfuse_logger +from langfuse.types import TraceContext try: import langchain @@ -132,6 +132,7 @@ def __init__( LangfuseRetriever, ], ] = {} + self._child_to_parent_run_id_map: Dict[UUID, Optional[UUID]] = {} self.context_tokens: Dict[UUID, Token] = {} self.prompt_to_parent_run_map: Dict[UUID, Any] = {} self.updated_completion_start_time_memo: Set[UUID] = set() @@ -302,6 +303,8 @@ def on_chain_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: self._log_debug_event( "on_chain_start", run_id, parent_run_id, inputs=inputs @@ -480,6 +483,8 @@ def on_agent_action( **kwargs: Any, ) -> Any: """Run on agent action.""" + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: self._log_debug_event( "on_agent_action", run_id, parent_run_id, action=action @@ -560,6 +565,10 @@ def on_chain_end( except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._reset() + def on_chain_error( self, error: BaseException, @@ -603,6 +612,8 @@ def on_chat_model_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: self._log_debug_event( "on_chat_model_start", run_id, parent_run_id, messages=messages @@ -635,6 +646,8 @@ def on_llm_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: self._log_debug_event( "on_llm_start", run_id, parent_run_id, prompts=prompts @@ -662,6 +675,8 @@ def on_tool_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: self._log_debug_event( "on_tool_start", run_id, parent_run_id, input_str=input_str @@ -704,6 +719,8 @@ def on_retriever_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: self._log_debug_event( "on_retriever_start", run_id, parent_run_id, query=query @@ -809,6 +826,8 @@ def __on_llm_action( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> None: + self._child_to_parent_run_id_map[run_id] = parent_run_id + try: tools = kwargs.get("invocation_params", {}).get("tools", None) if tools and isinstance(tools, list): @@ -817,14 +836,23 @@ def __on_llm_action( model_name = self._parse_model_and_log_errors( serialized=serialized, metadata=metadata, kwargs=kwargs ) - registered_prompt = ( - self.prompt_to_parent_run_map.get(parent_run_id) - if parent_run_id is not None - else None - ) - if registered_prompt: - self._deregister_langfuse_prompt(parent_run_id) + registered_prompt = None + current_parent_run_id = parent_run_id + + # Check all parents for registered prompt + while current_parent_run_id is not None: + registered_prompt = self.prompt_to_parent_run_map.get( + current_parent_run_id + ) + + if registered_prompt: + self._deregister_langfuse_prompt(current_parent_run_id) + break + else: + current_parent_run_id = self._child_to_parent_run_id_map.get( + current_parent_run_id, None + ) content = { "name": self.get_langchain_run_name(serialized, **kwargs), @@ -956,6 +984,9 @@ def on_llm_end( finally: self.updated_completion_start_time_memo.discard(run_id) + if parent_run_id is None: + self._reset() + def on_llm_error( self, error: BaseException, @@ -980,6 +1011,9 @@ def on_llm_error( except Exception as e: langfuse_logger.exception(e) + def _reset(self) -> None: + self._child_to_parent_run_id_map = {} + def __join_tags_and_metadata( self, tags: Optional[List[str]] = None, @@ -1047,7 +1081,7 @@ def _log_debug_event( **kwargs: Any, ) -> None: langfuse_logger.debug( - f"Event: {event_name}, run_id: {str(run_id)[:5]}, parent_run_id: {str(parent_run_id)[:5]}" + f"Event: {event_name}, run_id: {run_id}, parent_run_id: {parent_run_id}" ) @@ -1210,7 +1244,9 @@ def _parse_usage_model(usage: Union[pydantic.BaseModel, dict]) -> Any: usage_model["input"] = max(0, usage_model["input"] - value) if f"input_modality_{item['modality']}" in usage_model: - usage_model[f"input_modality_{item['modality']}"] = max(0, usage_model[f"input_modality_{item['modality']}"] - value) + usage_model[f"input_modality_{item['modality']}"] = max( + 0, usage_model[f"input_modality_{item['modality']}"] - value + ) usage_model = {k: v for k, v in usage_model.items() if isinstance(v, int)} From b1cb62a02ac8abd7e826d9e6631bab57ee6cad72 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 19 Dec 2025 14:30:59 +0000 Subject: [PATCH 145/296] chore: release v3.11.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index bd30a827f..fcfc10f1e 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.11.0" +__version__ = "3.11.1" diff --git a/pyproject.toml b/pyproject.toml index 97866a2bc..b4d05a226 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.11.0" +version = "3.11.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 600325dbe1e7f731cffa4ca986af15c69d949099 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 22 Dec 2025 10:25:00 +0100 Subject: [PATCH 146/296] fix(client): reuse httpx client in get_client (#1482) --- langfuse/_client/get_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index da1c3c994..9c426d9b3 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -53,6 +53,7 @@ def _create_client_from_instance( blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes, additional_headers=instance.additional_headers, tracer_provider=instance.tracer_provider, + httpx_client=instance.httpx_client, ) From 405827c5cad33e8b48ce4b991c595a3cf4c03ef3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:35:39 +0100 Subject: [PATCH 147/296] chore(deps-dev): bump langchain-core from 1.0.7 to 1.2.5 (#1488) Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 1.0.7 to 1.2.5. - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.0.7...langchain-core==1.2.5) --- updated-dependencies: - dependency-name: langchain-core dependency-version: 1.2.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1ff0fca7f..c5161e813 100644 --- a/poetry.lock +++ b/poetry.lock @@ -709,14 +709,14 @@ xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "1.0.7" +version = "1.2.5" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["dev"] files = [ - {file = "langchain_core-1.0.7-py3-none-any.whl", hash = "sha256:76af258b0e95a7915b8e301706a45ded50c75b80ff35329394d4df964416e32a"}, - {file = "langchain_core-1.0.7.tar.gz", hash = "sha256:6c64399cb0f163a7e45a764cce75d80fd08b82f4e0274ca892cfbcaa2f29200b"}, + {file = "langchain_core-1.2.5-py3-none-any.whl", hash = "sha256:3255944ef4e21b2551facb319bfc426057a40247c0a05de5bd6f2fc021fbfa34"}, + {file = "langchain_core-1.2.5.tar.gz", hash = "sha256:d674f6df42f07e846859b9d3afe547cad333d6bf9763e92c88eb4f8aaedcd3cc"}, ] [package.dependencies] @@ -727,6 +727,7 @@ pydantic = ">=2.7.4,<3.0.0" pyyaml = ">=5.3.0,<7.0.0" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7.0,<5.0.0" +uuid-utils = ">=0.12.0,<1.0" [[package]] name = "langchain-openai" @@ -2466,6 +2467,38 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] +[[package]] +name = "uuid-utils" +version = "0.12.0" +description = "Drop-in replacement for Python UUID with bindings in Rust" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514"}, + {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8fce83ecb3b16af29c7809669056c4b6e7cc912cab8c6d07361645de12dd79"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec921769afcb905035d785582b0791d02304a7850fbd6ce924c1a8976380dfc6"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f3b060330f5899a92d5c723547dc6a95adef42433e9748f14c66859a7396664"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:908dfef7f0bfcf98d406e5dc570c25d2f2473e49b376de41792b6e96c1d5d291"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c6a24148926bd0ca63e8a2dabf4cc9dc329a62325b3ad6578ecd60fbf926506"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:64a91e632669f059ef605f1771d28490b1d310c26198e46f754e8846dddf12f4"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:93c082212470bb4603ca3975916c205a9d7ef1443c0acde8fbd1e0f5b36673c7"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:431b1fb7283ba974811b22abd365f2726f8f821ab33f0f715be389640e18d039"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd7838c40149100299fa37cbd8bab5ee382372e8e65a148002a37d380df7c8"}, + {file = "uuid_utils-0.12.0-cp39-abi3-win32.whl", hash = "sha256:487f17c0fee6cbc1d8b90fe811874174a9b1b5683bf2251549e302906a50fed3"}, + {file = "uuid_utils-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:9598e7c9da40357ae8fffc5d6938b1a7017f09a1acbcc95e14af8c65d48c655a"}, + {file = "uuid_utils-0.12.0-cp39-abi3-win_arm64.whl", hash = "sha256:c9bea7c5b2aa6f57937ebebeee4d4ef2baad10f86f1b97b58a3f6f34c14b4e84"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e2209d361f2996966ab7114f49919eb6aaeabc6041672abbbbf4fdbb8ec1acc0"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d9636bcdbd6cfcad2b549c352b669412d0d1eb09be72044a2f13e498974863cd"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cd8543a3419251fb78e703ce3b15fdfafe1b7c542cf40caf0775e01db7e7674"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e98db2d8977c052cb307ae1cb5cc37a21715e8d415dbc65863b039397495a013"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8f2bdf5e4ffeb259ef6d15edae92aed60a1d6f07cbfab465d836f6b12b48da8"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c3ec53c0cb15e1835870c139317cc5ec06e35aa22843e3ed7d9c74f23f23898"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84e5c0eba209356f7f389946a3a47b2cc2effd711b3fc7c7f155ad9f7d45e8a3"}, + {file = "uuid_utils-0.12.0.tar.gz", hash = "sha256:252bd3d311b5d6b7f5dfce7a5857e27bb4458f222586bb439463231e5a9cbd64"}, +] + [[package]] name = "virtualenv" version = "20.35.3" From 179374ac0500e6edc58a39703f3a5a40288f81d3 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 23 Dec 2025 20:42:43 +0000 Subject: [PATCH 148/296] chore: release v3.11.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index fcfc10f1e..680fafd8d 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.11.1" +__version__ = "3.11.2" diff --git a/pyproject.toml b/pyproject.toml index b4d05a226..f5822d633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.11.1" +version = "3.11.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 3008a5372510328f94af75541c54ad3352bd7b89 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Sat, 3 Jan 2026 07:55:31 +0100 Subject: [PATCH 149/296] feat(api): update API spec from langfuse/langfuse d59b6a3 (#1489) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 4 ++ langfuse/api/reference.md | 18 +++++-- langfuse/api/resources/__init__.py | 4 ++ langfuse/api/resources/commons/__init__.py | 2 + .../api/resources/commons/types/__init__.py | 2 + .../resources/commons/types/score_config.py | 4 +- .../commons/types/score_config_data_type.py | 25 ++++++++++ .../commons/types/score_data_type.py | 4 ++ .../resources/ingestion/types/score_body.py | 6 ++- langfuse/api/resources/metrics/client.py | 4 ++ langfuse/api/resources/observations/client.py | 8 ++- langfuse/api/resources/projects/__init__.py | 2 + .../api/resources/projects/types/__init__.py | 2 + .../resources/projects/types/organization.py | 50 +++++++++++++++++++ .../api/resources/projects/types/project.py | 6 +++ .../api/resources/score_configs/client.py | 8 +-- .../types/create_score_config_request.py | 4 +- langfuse/api/resources/score_v_2/client.py | 10 ++++ 18 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 langfuse/api/resources/commons/types/score_config_data_type.py create mode 100644 langfuse/api/resources/projects/types/organization.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 9ec280087..835bdfefa 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -137,6 +137,7 @@ OpenAiResponseUsageSchema, OpenAiUsage, OptionalObservationBody, + Organization, OrganizationApiKey, OrganizationApiKeysResponse, OrganizationProject, @@ -187,6 +188,7 @@ Score, ScoreBody, ScoreConfig, + ScoreConfigDataType, ScoreConfigs, ScoreDataType, ScoreEvent, @@ -392,6 +394,7 @@ "OpenAiResponseUsageSchema", "OpenAiUsage", "OptionalObservationBody", + "Organization", "OrganizationApiKey", "OrganizationApiKeysResponse", "OrganizationProject", @@ -442,6 +445,7 @@ "Score", "ScoreBody", "ScoreConfig", + "ScoreConfigDataType", "ScoreConfigs", "ScoreDataType", "ScoreEvent", diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index b84696be4..6598e7cc4 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -3191,6 +3191,8 @@ JSON string containing the query parameters with the following structure: Get metrics from the Langfuse project using a query object. +Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). @@ -4007,7 +4009,9 @@ client.observations.get(
-Get a list of observations +Get a list of observations. + +Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection.
@@ -6666,7 +6670,7 @@ Create a score configuration (config). Score configs are used to define the stru
```python -from langfuse import CreateScoreConfigRequest, ScoreDataType +from langfuse import CreateScoreConfigRequest, ScoreConfigDataType from langfuse.client import FernLangfuse client = FernLangfuse( @@ -6680,7 +6684,7 @@ client = FernLangfuse( client.score_configs.create( request=CreateScoreConfigRequest( name="name", - data_type=ScoreDataType.NUMERIC, + data_type=ScoreConfigDataType.NUMERIC, ), ) @@ -7157,6 +7161,14 @@ client.score_v_2.get()
+**fields:** `typing.Optional[str]` — Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + +
+
+ +
+
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index 1095119a7..55c4e012a 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -92,6 +92,7 @@ PricingTierOperator, Score, ScoreConfig, + ScoreConfigDataType, ScoreDataType, ScoreSource, ScoreV1, @@ -211,6 +212,7 @@ ApiKeyList, ApiKeyResponse, ApiKeySummary, + Organization, Project, ProjectDeletionResponse, Projects, @@ -412,6 +414,7 @@ "OpenAiResponseUsageSchema", "OpenAiUsage", "OptionalObservationBody", + "Organization", "OrganizationApiKey", "OrganizationApiKeysResponse", "OrganizationProject", @@ -462,6 +465,7 @@ "Score", "ScoreBody", "ScoreConfig", + "ScoreConfigDataType", "ScoreConfigs", "ScoreDataType", "ScoreEvent", diff --git a/langfuse/api/resources/commons/__init__.py b/langfuse/api/resources/commons/__init__.py index 77097ba49..9e522548e 100644 --- a/langfuse/api/resources/commons/__init__.py +++ b/langfuse/api/resources/commons/__init__.py @@ -32,6 +32,7 @@ PricingTierOperator, Score, ScoreConfig, + ScoreConfigDataType, ScoreDataType, ScoreSource, ScoreV1, @@ -92,6 +93,7 @@ "PricingTierOperator", "Score", "ScoreConfig", + "ScoreConfigDataType", "ScoreDataType", "ScoreSource", "ScoreV1", diff --git a/langfuse/api/resources/commons/types/__init__.py b/langfuse/api/resources/commons/types/__init__.py index ee7f5714b..b9063f3fb 100644 --- a/langfuse/api/resources/commons/types/__init__.py +++ b/langfuse/api/resources/commons/types/__init__.py @@ -31,6 +31,7 @@ from .pricing_tier_operator import PricingTierOperator from .score import Score, Score_Boolean, Score_Categorical, Score_Numeric from .score_config import ScoreConfig +from .score_config_data_type import ScoreConfigDataType from .score_data_type import ScoreDataType from .score_source import ScoreSource from .score_v_1 import ScoreV1, ScoreV1_Boolean, ScoreV1_Categorical, ScoreV1_Numeric @@ -73,6 +74,7 @@ "PricingTierOperator", "Score", "ScoreConfig", + "ScoreConfigDataType", "ScoreDataType", "ScoreSource", "ScoreV1", diff --git a/langfuse/api/resources/commons/types/score_config.py b/langfuse/api/resources/commons/types/score_config.py index 4a7b30e0e..2f7248143 100644 --- a/langfuse/api/resources/commons/types/score_config.py +++ b/langfuse/api/resources/commons/types/score_config.py @@ -6,7 +6,7 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 from .config_category import ConfigCategory -from .score_data_type import ScoreDataType +from .score_config_data_type import ScoreConfigDataType class ScoreConfig(pydantic_v1.BaseModel): @@ -19,7 +19,7 @@ class ScoreConfig(pydantic_v1.BaseModel): created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") project_id: str = pydantic_v1.Field(alias="projectId") - data_type: ScoreDataType = pydantic_v1.Field(alias="dataType") + data_type: ScoreConfigDataType = pydantic_v1.Field(alias="dataType") is_archived: bool = pydantic_v1.Field(alias="isArchived") """ Whether the score config is archived. Defaults to false diff --git a/langfuse/api/resources/commons/types/score_config_data_type.py b/langfuse/api/resources/commons/types/score_config_data_type.py new file mode 100644 index 000000000..a7c9e7251 --- /dev/null +++ b/langfuse/api/resources/commons/types/score_config_data_type.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import typing + +T_Result = typing.TypeVar("T_Result") + + +class ScoreConfigDataType(str, enum.Enum): + NUMERIC = "NUMERIC" + BOOLEAN = "BOOLEAN" + CATEGORICAL = "CATEGORICAL" + + def visit( + self, + numeric: typing.Callable[[], T_Result], + boolean: typing.Callable[[], T_Result], + categorical: typing.Callable[[], T_Result], + ) -> T_Result: + if self is ScoreConfigDataType.NUMERIC: + return numeric() + if self is ScoreConfigDataType.BOOLEAN: + return boolean() + if self is ScoreConfigDataType.CATEGORICAL: + return categorical() diff --git a/langfuse/api/resources/commons/types/score_data_type.py b/langfuse/api/resources/commons/types/score_data_type.py index c2eed12cd..67bd9958b 100644 --- a/langfuse/api/resources/commons/types/score_data_type.py +++ b/langfuse/api/resources/commons/types/score_data_type.py @@ -10,12 +10,14 @@ class ScoreDataType(str, enum.Enum): NUMERIC = "NUMERIC" BOOLEAN = "BOOLEAN" CATEGORICAL = "CATEGORICAL" + CORRECTION = "CORRECTION" def visit( self, numeric: typing.Callable[[], T_Result], boolean: typing.Callable[[], T_Result], categorical: typing.Callable[[], T_Result], + correction: typing.Callable[[], T_Result], ) -> T_Result: if self is ScoreDataType.NUMERIC: return numeric() @@ -23,3 +25,5 @@ def visit( return boolean() if self is ScoreDataType.CATEGORICAL: return categorical() + if self is ScoreDataType.CORRECTION: + return correction() diff --git a/langfuse/api/resources/ingestion/types/score_body.py b/langfuse/api/resources/ingestion/types/score_body.py index 549046564..8e72a5682 100644 --- a/langfuse/api/resources/ingestion/types/score_body.py +++ b/langfuse/api/resources/ingestion/types/score_body.py @@ -33,7 +33,11 @@ class ScoreBody(pydantic_v1.BaseModel): dataset_run_id: typing.Optional[str] = pydantic_v1.Field( alias="datasetRunId", default=None ) - name: str + name: str = pydantic_v1.Field() + """ + The name of the score. Always overrides "output" for correction scores. + """ + environment: typing.Optional[str] = None queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) """ diff --git a/langfuse/api/resources/metrics/client.py b/langfuse/api/resources/metrics/client.py index 471f5182e..098c520c7 100644 --- a/langfuse/api/resources/metrics/client.py +++ b/langfuse/api/resources/metrics/client.py @@ -25,6 +25,8 @@ def metrics( """ Get metrics from the Langfuse project using a query object. + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). Parameters @@ -138,6 +140,8 @@ async def metrics( """ Get metrics from the Langfuse project using a query object. + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). Parameters diff --git a/langfuse/api/resources/observations/client.py b/langfuse/api/resources/observations/client.py index cac4efe4d..77cdd4ee6 100644 --- a/langfuse/api/resources/observations/client.py +++ b/langfuse/api/resources/observations/client.py @@ -111,7 +111,9 @@ def get_many( request_options: typing.Optional[RequestOptions] = None, ) -> ObservationsViews: """ - Get a list of observations + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- @@ -412,7 +414,9 @@ async def get_many( request_options: typing.Optional[RequestOptions] = None, ) -> ObservationsViews: """ - Get a list of observations + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- diff --git a/langfuse/api/resources/projects/__init__.py b/langfuse/api/resources/projects/__init__.py index 26c74c1c7..7161cfff1 100644 --- a/langfuse/api/resources/projects/__init__.py +++ b/langfuse/api/resources/projects/__init__.py @@ -5,6 +5,7 @@ ApiKeyList, ApiKeyResponse, ApiKeySummary, + Organization, Project, ProjectDeletionResponse, Projects, @@ -15,6 +16,7 @@ "ApiKeyList", "ApiKeyResponse", "ApiKeySummary", + "Organization", "Project", "ProjectDeletionResponse", "Projects", diff --git a/langfuse/api/resources/projects/types/__init__.py b/langfuse/api/resources/projects/types/__init__.py index c59b62a62..6fd44aa52 100644 --- a/langfuse/api/resources/projects/types/__init__.py +++ b/langfuse/api/resources/projects/types/__init__.py @@ -4,6 +4,7 @@ from .api_key_list import ApiKeyList from .api_key_response import ApiKeyResponse from .api_key_summary import ApiKeySummary +from .organization import Organization from .project import Project from .project_deletion_response import ProjectDeletionResponse from .projects import Projects @@ -13,6 +14,7 @@ "ApiKeyList", "ApiKeyResponse", "ApiKeySummary", + "Organization", "Project", "ProjectDeletionResponse", "Projects", diff --git a/langfuse/api/resources/projects/types/organization.py b/langfuse/api/resources/projects/types/organization.py new file mode 100644 index 000000000..1a46b6f6c --- /dev/null +++ b/langfuse/api/resources/projects/types/organization.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 + + +class Organization(pydantic_v1.BaseModel): + id: str = pydantic_v1.Field() + """ + The unique identifier of the organization + """ + + name: str = pydantic_v1.Field() + """ + The name of the organization + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/project.py b/langfuse/api/resources/projects/types/project.py index cf257d406..913dbb040 100644 --- a/langfuse/api/resources/projects/types/project.py +++ b/langfuse/api/resources/projects/types/project.py @@ -5,11 +5,17 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .organization import Organization class Project(pydantic_v1.BaseModel): id: str name: str + organization: Organization = pydantic_v1.Field() + """ + The organization this project belongs to + """ + metadata: typing.Dict[str, typing.Any] = pydantic_v1.Field() """ Metadata for the project diff --git a/langfuse/api/resources/score_configs/client.py b/langfuse/api/resources/score_configs/client.py index 7faea8312..9ac68ccce 100644 --- a/langfuse/api/resources/score_configs/client.py +++ b/langfuse/api/resources/score_configs/client.py @@ -48,7 +48,7 @@ def create( Examples -------- - from langfuse import CreateScoreConfigRequest, ScoreDataType + from langfuse import CreateScoreConfigRequest, ScoreConfigDataType from langfuse.client import FernLangfuse client = FernLangfuse( @@ -62,7 +62,7 @@ def create( client.score_configs.create( request=CreateScoreConfigRequest( name="name", - data_type=ScoreDataType.NUMERIC, + data_type=ScoreConfigDataType.NUMERIC, ), ) """ @@ -339,7 +339,7 @@ async def create( -------- import asyncio - from langfuse import CreateScoreConfigRequest, ScoreDataType + from langfuse import CreateScoreConfigRequest, ScoreConfigDataType from langfuse.client import AsyncFernLangfuse client = AsyncFernLangfuse( @@ -356,7 +356,7 @@ async def main() -> None: await client.score_configs.create( request=CreateScoreConfigRequest( name="name", - data_type=ScoreDataType.NUMERIC, + data_type=ScoreConfigDataType.NUMERIC, ), ) diff --git a/langfuse/api/resources/score_configs/types/create_score_config_request.py b/langfuse/api/resources/score_configs/types/create_score_config_request.py index e136af157..eb5c5e325 100644 --- a/langfuse/api/resources/score_configs/types/create_score_config_request.py +++ b/langfuse/api/resources/score_configs/types/create_score_config_request.py @@ -6,12 +6,12 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 from ...commons.types.config_category import ConfigCategory -from ...commons.types.score_data_type import ScoreDataType +from ...commons.types.score_config_data_type import ScoreConfigDataType class CreateScoreConfigRequest(pydantic_v1.BaseModel): name: str - data_type: ScoreDataType = pydantic_v1.Field(alias="dataType") + data_type: ScoreConfigDataType = pydantic_v1.Field(alias="dataType") categories: typing.Optional[typing.List[ConfigCategory]] = pydantic_v1.Field( default=None ) diff --git a/langfuse/api/resources/score_v_2/client.py b/langfuse/api/resources/score_v_2/client.py index 04569dc3a..2a1295432 100644 --- a/langfuse/api/resources/score_v_2/client.py +++ b/langfuse/api/resources/score_v_2/client.py @@ -46,6 +46,7 @@ def get( queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + fields: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> GetScoresResponse: """ @@ -107,6 +108,9 @@ def get( trace_tags : typing.Optional[typing.Union[str, typing.Sequence[str]]] Only scores linked to traces that include all of these tags will be returned. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -154,6 +158,7 @@ def get( "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, + "fields": fields, }, request_options=request_options, ) @@ -274,6 +279,7 @@ async def get( queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + fields: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> GetScoresResponse: """ @@ -335,6 +341,9 @@ async def get( trace_tags : typing.Optional[typing.Union[str, typing.Sequence[str]]] Only scores linked to traces that include all of these tags will be returned. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -390,6 +399,7 @@ async def main() -> None: "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, + "fields": fields, }, request_options=request_options, ) From 280b8315a84f2f5299a1712659296d2f4648d742 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 06:41:55 +0000 Subject: [PATCH 150/296] chore(deps): bump urllib3 from 2.6.0 to 2.6.3 (#1493) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.0 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.0...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index c5161e813..00e7d11a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2451,14 +2451,14 @@ typing-extensions = ">=4.12.0" [[package]] name = "urllib3" -version = "2.6.0" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"}, - {file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] From c7b6e12a21ea191e6c3f8e5840d831a91376452d Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 8 Jan 2026 10:30:26 +0100 Subject: [PATCH 151/296] feat(api): update API spec from langfuse/langfuse 41f064c (#1492) Co-authored-by: langfuse-bot Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/api/reference.md | 15 +++++++- .../api/resources/commons/types/base_score.py | 34 +++++++++++++++++-- .../resources/commons/types/base_score_v_1.py | 22 ++++++++++-- .../api/resources/commons/types/comment.py | 3 ++ .../api/resources/commons/types/dataset.py | 12 +++++-- .../resources/commons/types/dataset_item.py | 28 ++++++++++++--- .../resources/commons/types/dataset_run.py | 2 +- .../commons/types/dataset_run_item.py | 4 +++ langfuse/api/resources/commons/types/model.py | 4 +-- .../resources/commons/types/observation.py | 23 +++++-------- langfuse/api/resources/commons/types/score.py | 12 +++---- .../resources/commons/types/score_config.py | 5 ++- .../api/resources/commons/types/score_v_1.py | 12 +++---- .../api/resources/commons/types/session.py | 2 +- langfuse/api/resources/commons/types/trace.py | 8 ++--- .../commons/types/trace_with_details.py | 10 +++--- .../commons/types/trace_with_full_details.py | 6 ++-- langfuse/api/resources/commons/types/usage.py | 13 ++++--- .../api/resources/observations_v_2/client.py | 20 +++++++++-- .../types/get_scores_response_data.py | 12 +++---- 20 files changed, 177 insertions(+), 70 deletions(-) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 6598e7cc4..759ddbdec 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -3635,7 +3635,7 @@ Use the `fields` parameter to control which observation fields are returned: - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - `time` - completionStartTime, createdAt, updatedAt - `io` - input, output -- `metadata` - metadata +- `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - `usage` - usageDetails, costDetails, totalCost - `prompt` - promptId, promptName, promptVersion @@ -3699,6 +3699,19 @@ Example: "basic,usage,model"
+**expand_metadata:** `typing.Optional[str]` + +Comma-separated list of metadata keys to return non-truncated. +By default, metadata values over 200 characters are truncated. +Use this parameter to retrieve full values for specific keys. +Example: "key1,key2" + +
+
+ +
+
+ **limit:** `typing.Optional[int]` — Number of items to return per page. Maximum 1000, default 50.
diff --git a/langfuse/api/resources/commons/types/base_score.py b/langfuse/api/resources/commons/types/base_score.py index dd5449c83..c6a0d739a 100644 --- a/langfuse/api/resources/commons/types/base_score.py +++ b/langfuse/api/resources/commons/types/base_score.py @@ -11,15 +11,31 @@ class BaseScore(pydantic_v1.BaseModel): id: str trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) + """ + The trace ID associated with the score + """ + session_id: typing.Optional[str] = pydantic_v1.Field( alias="sessionId", default=None ) + """ + The session ID associated with the score + """ + observation_id: typing.Optional[str] = pydantic_v1.Field( alias="observationId", default=None ) + """ + The observation ID associated with the score + """ + dataset_run_id: typing.Optional[str] = pydantic_v1.Field( alias="datasetRunId", default=None ) + """ + The dataset run ID associated with the score + """ + name: str source: ScoreSource timestamp: dt.datetime @@ -28,8 +44,20 @@ class BaseScore(pydantic_v1.BaseModel): author_user_id: typing.Optional[str] = pydantic_v1.Field( alias="authorUserId", default=None ) - comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + """ + The user ID of the author + """ + + comment: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Comment on the score + """ + + metadata: typing.Any = pydantic_v1.Field() + """ + Metadata associated with the score + """ + config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) """ Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range @@ -40,7 +68,7 @@ class BaseScore(pydantic_v1.BaseModel): The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. """ - environment: typing.Optional[str] = pydantic_v1.Field(default=None) + environment: str = pydantic_v1.Field() """ The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. """ diff --git a/langfuse/api/resources/commons/types/base_score_v_1.py b/langfuse/api/resources/commons/types/base_score_v_1.py index 478dcc6e6..0350864dc 100644 --- a/langfuse/api/resources/commons/types/base_score_v_1.py +++ b/langfuse/api/resources/commons/types/base_score_v_1.py @@ -16,14 +16,30 @@ class BaseScoreV1(pydantic_v1.BaseModel): observation_id: typing.Optional[str] = pydantic_v1.Field( alias="observationId", default=None ) + """ + The observation ID associated with the score + """ + timestamp: dt.datetime created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") author_user_id: typing.Optional[str] = pydantic_v1.Field( alias="authorUserId", default=None ) - comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + """ + The user ID of the author + """ + + comment: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Comment on the score + """ + + metadata: typing.Any = pydantic_v1.Field() + """ + Metadata associated with the score + """ + config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) """ Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range @@ -34,7 +50,7 @@ class BaseScoreV1(pydantic_v1.BaseModel): The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. """ - environment: typing.Optional[str] = pydantic_v1.Field(default=None) + environment: str = pydantic_v1.Field() """ The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. """ diff --git a/langfuse/api/resources/commons/types/comment.py b/langfuse/api/resources/commons/types/comment.py index 4d8b1916a..bf8506797 100644 --- a/langfuse/api/resources/commons/types/comment.py +++ b/langfuse/api/resources/commons/types/comment.py @@ -19,6 +19,9 @@ class Comment(pydantic_v1.BaseModel): author_user_id: typing.Optional[str] = pydantic_v1.Field( alias="authorUserId", default=None ) + """ + The user ID of the comment author + """ def json(self, **kwargs: typing.Any) -> str: kwargs_with_defaults: typing.Any = { diff --git a/langfuse/api/resources/commons/types/dataset.py b/langfuse/api/resources/commons/types/dataset.py index 116bff135..db54a8ee2 100644 --- a/langfuse/api/resources/commons/types/dataset.py +++ b/langfuse/api/resources/commons/types/dataset.py @@ -10,8 +10,16 @@ class Dataset(pydantic_v1.BaseModel): id: str name: str - description: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + description: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Description of the dataset + """ + + metadata: typing.Any = pydantic_v1.Field() + """ + Metadata associated with the dataset + """ + input_schema: typing.Optional[typing.Any] = pydantic_v1.Field( alias="inputSchema", default=None ) diff --git a/langfuse/api/resources/commons/types/dataset_item.py b/langfuse/api/resources/commons/types/dataset_item.py index dd5f85e78..eadd57f64 100644 --- a/langfuse/api/resources/commons/types/dataset_item.py +++ b/langfuse/api/resources/commons/types/dataset_item.py @@ -11,17 +11,35 @@ class DatasetItem(pydantic_v1.BaseModel): id: str status: DatasetStatus - input: typing.Optional[typing.Any] = None - expected_output: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="expectedOutput", default=None - ) - metadata: typing.Optional[typing.Any] = None + input: typing.Any = pydantic_v1.Field() + """ + Input data for the dataset item + """ + + expected_output: typing.Any = pydantic_v1.Field(alias="expectedOutput") + """ + Expected output for the dataset item + """ + + metadata: typing.Any = pydantic_v1.Field() + """ + Metadata associated with the dataset item + """ + source_trace_id: typing.Optional[str] = pydantic_v1.Field( alias="sourceTraceId", default=None ) + """ + The trace ID that sourced this dataset item + """ + source_observation_id: typing.Optional[str] = pydantic_v1.Field( alias="sourceObservationId", default=None ) + """ + The observation ID that sourced this dataset item + """ + dataset_id: str = pydantic_v1.Field(alias="datasetId") dataset_name: str = pydantic_v1.Field(alias="datasetName") created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") diff --git a/langfuse/api/resources/commons/types/dataset_run.py b/langfuse/api/resources/commons/types/dataset_run.py index 74b1a2ac8..e130738de 100644 --- a/langfuse/api/resources/commons/types/dataset_run.py +++ b/langfuse/api/resources/commons/types/dataset_run.py @@ -23,7 +23,7 @@ class DatasetRun(pydantic_v1.BaseModel): Description of the run """ - metadata: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) + metadata: typing.Any = pydantic_v1.Field() """ Metadata of the dataset run """ diff --git a/langfuse/api/resources/commons/types/dataset_run_item.py b/langfuse/api/resources/commons/types/dataset_run_item.py index f1b3af163..ca41ae5c6 100644 --- a/langfuse/api/resources/commons/types/dataset_run_item.py +++ b/langfuse/api/resources/commons/types/dataset_run_item.py @@ -16,6 +16,10 @@ class DatasetRunItem(pydantic_v1.BaseModel): observation_id: typing.Optional[str] = pydantic_v1.Field( alias="observationId", default=None ) + """ + The observation ID associated with this run item + """ + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") diff --git a/langfuse/api/resources/commons/types/model.py b/langfuse/api/resources/commons/types/model.py index 1b83c2696..86fce3c2d 100644 --- a/langfuse/api/resources/commons/types/model.py +++ b/langfuse/api/resources/commons/types/model.py @@ -74,9 +74,7 @@ class Model(pydantic_v1.BaseModel): Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. """ - tokenizer_config: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="tokenizerConfig", default=None - ) + tokenizer_config: typing.Any = pydantic_v1.Field(alias="tokenizerConfig") """ Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. """ diff --git a/langfuse/api/resources/commons/types/observation.py b/langfuse/api/resources/commons/types/observation.py index b821476f9..1c343aa76 100644 --- a/langfuse/api/resources/commons/types/observation.py +++ b/langfuse/api/resources/commons/types/observation.py @@ -5,7 +5,6 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .map_value import MapValue from .observation_level import ObservationLevel from .usage import Usage @@ -55,14 +54,12 @@ class Observation(pydantic_v1.BaseModel): The model used for the observation """ - model_parameters: typing.Optional[typing.Dict[str, MapValue]] = pydantic_v1.Field( - alias="modelParameters", default=None - ) + model_parameters: typing.Any = pydantic_v1.Field(alias="modelParameters") """ The parameters of the model used for the observation """ - input: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) + input: typing.Any = pydantic_v1.Field() """ The input data of the observation """ @@ -72,17 +69,17 @@ class Observation(pydantic_v1.BaseModel): The version of the observation """ - metadata: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) + metadata: typing.Any = pydantic_v1.Field() """ Additional metadata of the observation """ - output: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) + output: typing.Any = pydantic_v1.Field() """ The output data of the observation """ - usage: typing.Optional[Usage] = pydantic_v1.Field(default=None) + usage: Usage = pydantic_v1.Field() """ (Deprecated. Use usageDetails and costDetails instead.) The usage data of the observation """ @@ -111,21 +108,17 @@ class Observation(pydantic_v1.BaseModel): The prompt ID associated with the observation """ - usage_details: typing.Optional[typing.Dict[str, int]] = pydantic_v1.Field( - alias="usageDetails", default=None - ) + usage_details: typing.Dict[str, int] = pydantic_v1.Field(alias="usageDetails") """ The usage details of the observation. Key is the name of the usage metric, value is the number of units consumed. The total key is the sum of all (non-total) usage metrics or the total value ingested. """ - cost_details: typing.Optional[typing.Dict[str, float]] = pydantic_v1.Field( - alias="costDetails", default=None - ) + cost_details: typing.Dict[str, float] = pydantic_v1.Field(alias="costDetails") """ The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested. """ - environment: typing.Optional[str] = pydantic_v1.Field(default=None) + environment: str = pydantic_v1.Field() """ The environment from which this observation originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. """ diff --git a/langfuse/api/resources/commons/types/score.py b/langfuse/api/resources/commons/types/score.py index f0b866067..8d54b6575 100644 --- a/langfuse/api/resources/commons/types/score.py +++ b/langfuse/api/resources/commons/types/score.py @@ -32,10 +32,10 @@ class Score_Numeric(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field( alias="dataType", default="NUMERIC" ) @@ -97,10 +97,10 @@ class Score_Categorical(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field( alias="dataType", default="CATEGORICAL" ) @@ -162,10 +162,10 @@ class Score_Boolean(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field( alias="dataType", default="BOOLEAN" ) diff --git a/langfuse/api/resources/commons/types/score_config.py b/langfuse/api/resources/commons/types/score_config.py index 2f7248143..1fda37a09 100644 --- a/langfuse/api/resources/commons/types/score_config.py +++ b/langfuse/api/resources/commons/types/score_config.py @@ -46,7 +46,10 @@ class ScoreConfig(pydantic_v1.BaseModel): Configures custom categories for categorical scores """ - description: typing.Optional[str] = None + description: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Description of the score config + """ def json(self, **kwargs: typing.Any) -> str: kwargs_with_defaults: typing.Any = { diff --git a/langfuse/api/resources/commons/types/score_v_1.py b/langfuse/api/resources/commons/types/score_v_1.py index 191e0d96f..74c3f53f9 100644 --- a/langfuse/api/resources/commons/types/score_v_1.py +++ b/langfuse/api/resources/commons/types/score_v_1.py @@ -26,10 +26,10 @@ class ScoreV1_Numeric(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field( alias="dataType", default="NUMERIC" ) @@ -85,10 +85,10 @@ class ScoreV1_Categorical(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field( alias="dataType", default="CATEGORICAL" ) @@ -144,10 +144,10 @@ class ScoreV1_Boolean(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field( alias="dataType", default="BOOLEAN" ) diff --git a/langfuse/api/resources/commons/types/session.py b/langfuse/api/resources/commons/types/session.py index 46a0a6b96..ed1557460 100644 --- a/langfuse/api/resources/commons/types/session.py +++ b/langfuse/api/resources/commons/types/session.py @@ -11,7 +11,7 @@ class Session(pydantic_v1.BaseModel): id: str created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") project_id: str = pydantic_v1.Field(alias="projectId") - environment: typing.Optional[str] = pydantic_v1.Field(default=None) + environment: str = pydantic_v1.Field() """ The environment from which this session originated. """ diff --git a/langfuse/api/resources/commons/types/trace.py b/langfuse/api/resources/commons/types/trace.py index d977ed3d7..725ad106d 100644 --- a/langfuse/api/resources/commons/types/trace.py +++ b/langfuse/api/resources/commons/types/trace.py @@ -60,17 +60,17 @@ class Trace(pydantic_v1.BaseModel): The metadata associated with the trace. Can be any JSON. """ - tags: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) + tags: typing.List[str] = pydantic_v1.Field() """ - The tags associated with the trace. Can be an array of strings or null. + The tags associated with the trace. """ - public: typing.Optional[bool] = pydantic_v1.Field(default=None) + public: bool = pydantic_v1.Field() """ Public traces are accessible via url without login """ - environment: typing.Optional[str] = pydantic_v1.Field(default=None) + environment: str = pydantic_v1.Field() """ The environment from which this trace originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. """ diff --git a/langfuse/api/resources/commons/types/trace_with_details.py b/langfuse/api/resources/commons/types/trace_with_details.py index 5ffe6f218..795c43adc 100644 --- a/langfuse/api/resources/commons/types/trace_with_details.py +++ b/langfuse/api/resources/commons/types/trace_with_details.py @@ -14,22 +14,24 @@ class TraceWithDetails(Trace): Path of trace in Langfuse UI """ - latency: float = pydantic_v1.Field() + latency: typing.Optional[float] = pydantic_v1.Field(default=None) """ Latency of trace in seconds """ - total_cost: float = pydantic_v1.Field(alias="totalCost") + total_cost: typing.Optional[float] = pydantic_v1.Field( + alias="totalCost", default=None + ) """ Cost of trace in USD """ - observations: typing.List[str] = pydantic_v1.Field() + observations: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) """ List of observation ids """ - scores: typing.List[str] = pydantic_v1.Field() + scores: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) """ List of score ids """ diff --git a/langfuse/api/resources/commons/types/trace_with_full_details.py b/langfuse/api/resources/commons/types/trace_with_full_details.py index 2c6a99402..eb2848fa1 100644 --- a/langfuse/api/resources/commons/types/trace_with_full_details.py +++ b/langfuse/api/resources/commons/types/trace_with_full_details.py @@ -16,12 +16,14 @@ class TraceWithFullDetails(Trace): Path of trace in Langfuse UI """ - latency: float = pydantic_v1.Field() + latency: typing.Optional[float] = pydantic_v1.Field(default=None) """ Latency of trace in seconds """ - total_cost: float = pydantic_v1.Field(alias="totalCost") + total_cost: typing.Optional[float] = pydantic_v1.Field( + alias="totalCost", default=None + ) """ Cost of trace in USD """ diff --git a/langfuse/api/resources/commons/types/usage.py b/langfuse/api/resources/commons/types/usage.py index c38330494..c26620b5b 100644 --- a/langfuse/api/resources/commons/types/usage.py +++ b/langfuse/api/resources/commons/types/usage.py @@ -5,7 +5,6 @@ from ....core.datetime_utils import serialize_datetime from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .model_usage_unit import ModelUsageUnit class Usage(pydantic_v1.BaseModel): @@ -13,22 +12,26 @@ class Usage(pydantic_v1.BaseModel): (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost """ - input: typing.Optional[int] = pydantic_v1.Field(default=None) + input: int = pydantic_v1.Field() """ Number of input units (e.g. tokens) """ - output: typing.Optional[int] = pydantic_v1.Field(default=None) + output: int = pydantic_v1.Field() """ Number of output units (e.g. tokens) """ - total: typing.Optional[int] = pydantic_v1.Field(default=None) + total: int = pydantic_v1.Field() """ Defaults to input+output if not set """ - unit: typing.Optional[ModelUsageUnit] = None + unit: typing.Optional[str] = pydantic_v1.Field(default=None) + """ + Unit of measurement + """ + input_cost: typing.Optional[float] = pydantic_v1.Field( alias="inputCost", default=None ) diff --git a/langfuse/api/resources/observations_v_2/client.py b/langfuse/api/resources/observations_v_2/client.py index e6796fd46..ea9599c69 100644 --- a/langfuse/api/resources/observations_v_2/client.py +++ b/langfuse/api/resources/observations_v_2/client.py @@ -26,6 +26,7 @@ def get_many( self, *, fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, cursor: typing.Optional[str] = None, parse_io_as_json: typing.Optional[bool] = None, @@ -56,7 +57,7 @@ def get_many( - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - `time` - completionStartTime, createdAt, updatedAt - `io` - input, output - - `metadata` - metadata + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - `usage` - usageDetails, costDetails, totalCost - `prompt` - promptId, promptName, promptVersion @@ -76,6 +77,12 @@ def get_many( If not specified, `core` and `basic` field groups are returned. Example: "basic,usage,model" + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" + limit : typing.Optional[int] Number of items to return per page. Maximum 1000, default 50. @@ -234,6 +241,7 @@ def get_many( method="GET", params={ "fields": fields, + "expandMetadata": expand_metadata, "limit": limit, "cursor": cursor, "parseIoAsJson": parse_io_as_json, @@ -292,6 +300,7 @@ async def get_many( self, *, fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, cursor: typing.Optional[str] = None, parse_io_as_json: typing.Optional[bool] = None, @@ -322,7 +331,7 @@ async def get_many( - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - `time` - completionStartTime, createdAt, updatedAt - `io` - input, output - - `metadata` - metadata + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - `usage` - usageDetails, costDetails, totalCost - `prompt` - promptId, promptName, promptVersion @@ -342,6 +351,12 @@ async def get_many( If not specified, `core` and `basic` field groups are returned. Example: "basic,usage,model" + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" + limit : typing.Optional[int] Number of items to return per page. Maximum 1000, default 50. @@ -508,6 +523,7 @@ async def main() -> None: method="GET", params={ "fields": fields, + "expandMetadata": expand_metadata, "limit": limit, "cursor": cursor, "parseIoAsJson": parse_io_as_json, diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py index e09f31cb9..965a01c80 100644 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py +++ b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py @@ -34,10 +34,10 @@ class GetScoresResponseData_Numeric(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field( alias="dataType", default="NUMERIC" ) @@ -100,10 +100,10 @@ class GetScoresResponseData_Categorical(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field( alias="dataType", default="CATEGORICAL" ) @@ -166,10 +166,10 @@ class GetScoresResponseData_Boolean(pydantic_v1.BaseModel): alias="authorUserId", default=None ) comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None + metadata: typing.Any config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: typing.Optional[str] = None + environment: str data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field( alias="dataType", default="BOOLEAN" ) From 9843ecb388dac097b337415d5088065633cc8b6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:40:48 +0000 Subject: [PATCH 152/296] chore(deps-dev): bump werkzeug from 3.1.4 to 3.1.5 (#1494) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.4 to 3.1.5. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.1.4...3.1.5) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 00e7d11a6..76c603e18 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2523,14 +2523,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "werkzeug" -version = "3.1.4" +version = "3.1.5" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"}, - {file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"}, + {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"}, + {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"}, ] [package.dependencies] From 25c5ef9a5c1fa6211a23b38c769097e1cddee38d Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Sat, 10 Jan 2026 10:30:45 +0100 Subject: [PATCH 153/296] feat(api): update API spec from langfuse/langfuse 99ffc45 (#1495) Co-authored-by: langfuse-bot --- langfuse/api/reference.md | 10 +++++++--- langfuse/api/resources/projects/client.py | 24 +++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 759ddbdec..19870d547 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -5222,7 +5222,6 @@ client = FernLangfuse( client.projects.update( project_id="projectId", name="name", - retention=1, ) ``` @@ -5255,7 +5254,7 @@ client.projects.update(
-**retention:** `int` — Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. +**metadata:** `typing.Optional[typing.Dict[str, typing.Any]]` — Optional metadata for the project
@@ -5263,7 +5262,12 @@ client.projects.update(
-**metadata:** `typing.Optional[typing.Dict[str, typing.Any]]` — Optional metadata for the project +**retention:** `typing.Optional[int]` + +Number of days to retain data. +Must be 0 or at least 3 days. +Requires data-retention entitlement for non-zero values. +Optional. Will retain existing retention setting if omitted.
diff --git a/langfuse/api/resources/projects/client.py b/langfuse/api/resources/projects/client.py index 5af232dfb..3db173d75 100644 --- a/langfuse/api/resources/projects/client.py +++ b/langfuse/api/resources/projects/client.py @@ -169,8 +169,8 @@ def update( project_id: str, *, name: str, - retention: int, metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + retention: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Project: """ @@ -182,12 +182,15 @@ def update( name : str - retention : int - Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. - metadata : typing.Optional[typing.Dict[str, typing.Any]] Optional metadata for the project + retention : typing.Optional[int] + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -210,7 +213,6 @@ def update( client.projects.update( project_id="projectId", name="name", - retention=1, ) """ _response = self._client_wrapper.httpx_client.request( @@ -698,8 +700,8 @@ async def update( project_id: str, *, name: str, - retention: int, metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + retention: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Project: """ @@ -711,12 +713,15 @@ async def update( name : str - retention : int - Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. - metadata : typing.Optional[typing.Dict[str, typing.Any]] Optional metadata for the project + retention : typing.Optional[int] + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -744,7 +749,6 @@ async def main() -> None: await client.projects.update( project_id="projectId", name="name", - retention=1, ) From 898a10cde196772f502f3621439764be3aab34cc Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:53:14 +0200 Subject: [PATCH 154/296] fix(experiments): move evaluations to root experiment span (#1497) --- langfuse/_client/client.py | 198 +++++++++++++++++++------------------ tests/test_prompt.py | 2 +- 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index a3f653ada..39679c79b 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2866,17 +2866,17 @@ async def _process_experiment_item( } ) - with _propagate_attributes( - experiment=PropagatedExperimentAttributes( - experiment_id=experiment_id, - experiment_name=experiment_run_name, - experiment_metadata=_serialize(experiment_metadata), - experiment_dataset_id=dataset_id, - experiment_item_id=experiment_item_id, - experiment_item_metadata=_serialize(item_metadata), - experiment_item_root_observation_id=span.id, - ) - ): + propagated_experiment_attributes = PropagatedExperimentAttributes( + experiment_id=experiment_id, + experiment_name=experiment_run_name, + experiment_metadata=_serialize(experiment_metadata), + experiment_dataset_id=dataset_id, + experiment_item_id=experiment_item_id, + experiment_item_metadata=_serialize(item_metadata), + experiment_item_root_observation_id=span.id, + ) + + with _propagate_attributes(experiment=propagated_experiment_attributes): output = await _run_task(task, item) span.update( @@ -2891,95 +2891,101 @@ async def _process_experiment_item( ) raise e - # Run evaluators - evaluations = [] - - for evaluator in evaluators: - try: - eval_metadata: Optional[Dict[str, Any]] = None + # Run evaluators + evaluations = [] - if isinstance(item, dict): - eval_metadata = item.get("metadata") - elif hasattr(item, "metadata"): - eval_metadata = item.metadata + for evaluator in evaluators: + try: + eval_metadata: Optional[Dict[str, Any]] = None - eval_results = await _run_evaluator( - evaluator, - input=input_data, - output=output, - expected_output=expected_output, - metadata=eval_metadata, - ) - evaluations.extend(eval_results) - - # Store evaluations as scores - for evaluation in eval_results: - self.create_score( - trace_id=trace_id, - observation_id=span.id, - name=evaluation.name, - value=evaluation.value, # type: ignore - comment=evaluation.comment, - metadata=evaluation.metadata, - config_id=evaluation.config_id, - data_type=evaluation.data_type, # type: ignore - ) - - except Exception as e: - langfuse_logger.error(f"Evaluator failed: {e}") - - # Run composite evaluator if provided and we have evaluations - if composite_evaluator and evaluations: - try: - composite_eval_metadata: Optional[Dict[str, Any]] = None - if isinstance(item, dict): - composite_eval_metadata = item.get("metadata") - elif hasattr(item, "metadata"): - composite_eval_metadata = item.metadata + if isinstance(item, dict): + eval_metadata = item.get("metadata") + elif hasattr(item, "metadata"): + eval_metadata = item.metadata - result = composite_evaluator( - input=input_data, - output=output, - expected_output=expected_output, - metadata=composite_eval_metadata, - evaluations=evaluations, - ) - - # Handle async composite evaluators - if asyncio.iscoroutine(result): - result = await result - - # Normalize to list - composite_evals: List[Evaluation] = [] - if isinstance(result, (dict, Evaluation)): - composite_evals = [result] # type: ignore - elif isinstance(result, list): - composite_evals = result # type: ignore - - # Store composite evaluations as scores and add to evaluations list - for composite_evaluation in composite_evals: - self.create_score( - trace_id=trace_id, - observation_id=span.id, - name=composite_evaluation.name, - value=composite_evaluation.value, # type: ignore - comment=composite_evaluation.comment, - metadata=composite_evaluation.metadata, - config_id=composite_evaluation.config_id, - data_type=composite_evaluation.data_type, # type: ignore - ) - evaluations.append(composite_evaluation) - - except Exception as e: - langfuse_logger.error(f"Composite evaluator failed: {e}") + with _propagate_attributes( + experiment=propagated_experiment_attributes + ): + eval_results = await _run_evaluator( + evaluator, + input=input_data, + output=output, + expected_output=expected_output, + metadata=eval_metadata, + ) + evaluations.extend(eval_results) + + # Store evaluations as scores + for evaluation in eval_results: + self.create_score( + trace_id=trace_id, + observation_id=span.id, + name=evaluation.name, + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=evaluation.metadata, + config_id=evaluation.config_id, + data_type=evaluation.data_type, # type: ignore + ) + + except Exception as e: + langfuse_logger.error(f"Evaluator failed: {e}") + + # Run composite evaluator if provided and we have evaluations + if composite_evaluator and evaluations: + try: + composite_eval_metadata: Optional[Dict[str, Any]] = None + if isinstance(item, dict): + composite_eval_metadata = item.get("metadata") + elif hasattr(item, "metadata"): + composite_eval_metadata = item.metadata + + with _propagate_attributes( + experiment=propagated_experiment_attributes + ): + result = composite_evaluator( + input=input_data, + output=output, + expected_output=expected_output, + metadata=composite_eval_metadata, + evaluations=evaluations, + ) - return ExperimentItemResult( - item=item, - output=output, - evaluations=evaluations, - trace_id=trace_id, - dataset_run_id=dataset_run_id, - ) + # Handle async composite evaluators + if asyncio.iscoroutine(result): + result = await result + + # Normalize to list + composite_evals: List[Evaluation] = [] + if isinstance(result, (dict, Evaluation)): + composite_evals = [result] # type: ignore + elif isinstance(result, list): + composite_evals = result # type: ignore + + # Store composite evaluations as scores and add to evaluations list + for composite_evaluation in composite_evals: + self.create_score( + trace_id=trace_id, + observation_id=span.id, + name=composite_evaluation.name, + value=composite_evaluation.value, # type: ignore + comment=composite_evaluation.comment, + metadata=composite_evaluation.metadata, + config_id=composite_evaluation.config_id, + data_type=composite_evaluation.data_type, # type: ignore + ) + evaluations.append(composite_evaluation) + + except Exception as e: + langfuse_logger.error(f"Composite evaluator failed: {e}") + + return ExperimentItemResult( + item=item, + output=output, + evaluations=evaluations, + trace_id=trace_id, + dataset_run_id=dataset_run_id, + ) def _create_experiment_run_name( self, *, name: Optional[str] = None, run_name: Optional[str] = None diff --git a/tests/test_prompt.py b/tests/test_prompt.py index bc3a5b7eb..6ba5ab85a 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -682,7 +682,7 @@ def test_prompt_end_to_end(): @pytest.fixture def langfuse(): from langfuse._client.resource_manager import LangfuseResourceManager - + langfuse_instance = Langfuse() langfuse_instance.api = Mock() From f4148bc5d91551fc0a9ebb292837bdfd2c1b051c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:27:24 +0200 Subject: [PATCH 155/296] feat(client): add score metadata argument to span client score methods (#1498) --- langfuse/_client/span.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 2f0000e41..92c1556ca 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -277,6 +277,7 @@ def score( comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, + metadata: Optional[Any] = None, ) -> None: ... @overload @@ -290,6 +291,7 @@ def score( comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, + metadata: Optional[Any] = None, ) -> None: ... def score( @@ -302,6 +304,7 @@ def score( comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, + metadata: Optional[Any] = None, ) -> None: """Create a score for this specific span. @@ -316,6 +319,7 @@ def score( comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse timestamp: Optional timestamp for the score (defaults to current UTC time) + metadata: Optional metadata to be attached to the score Example: ```python @@ -342,6 +346,7 @@ def score( comment=comment, config_id=config_id, timestamp=timestamp, + metadata=metadata, ) @overload @@ -355,6 +360,7 @@ def score_trace( comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, + metadata: Optional[Any] = None, ) -> None: ... @overload @@ -368,6 +374,7 @@ def score_trace( comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, + metadata: Optional[Any] = None, ) -> None: ... def score_trace( @@ -380,6 +387,7 @@ def score_trace( comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, + metadata: Optional[Any] = None, ) -> None: """Create a score for the entire trace that this span belongs to. @@ -395,6 +403,7 @@ def score_trace( comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse timestamp: Optional timestamp for the score (defaults to current UTC time) + metadata: Optional metadata to be attached to the score Example: ```python @@ -420,6 +429,7 @@ def score_trace( comment=comment, config_id=config_id, timestamp=timestamp, + metadata=metadata, ) def _set_processed_span_attributes( From ea5658fdfb5bd4218264a4c4e8df41a05b9ef311 Mon Sep 17 00:00:00 2001 From: marliessophie <74332854+marliessophie@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:32:45 +0100 Subject: [PATCH 156/296] feat(client): add methods to langfuse client to fetch and delete dataset runs (#1453) --- langfuse/_client/client.py | 77 ++++++++++++++++++++++++++ tests/test_datasets.py | 110 ++++++++++++++++++++++++++++++++++++- 2 files changed, 186 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 39679c79b..7ee2f962f 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -79,6 +79,11 @@ from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache from langfuse.api.resources.commons.errors.error import Error +from langfuse.api.resources.commons.types import DatasetRunWithItems +from langfuse.api.resources.datasets.types import ( + DeleteDatasetRunResponse, + PaginatedDatasetRuns, +) from langfuse.api.resources.commons.errors.not_found_error import NotFoundError from langfuse.api.resources.ingestion.types.score_body import ScoreBody from langfuse.api.resources.prompts.types import ( @@ -2461,6 +2466,78 @@ def get_dataset( handle_fern_exception(e) raise e + def get_dataset_run( + self, *, dataset_name: str, run_name: str + ) -> DatasetRunWithItems: + """Fetch a dataset run by dataset name and run name. + + Args: + dataset_name (str): The name of the dataset. + run_name (str): The name of the run. + + Returns: + DatasetRunWithItems: The dataset run with its items. + """ + try: + return self.api.datasets.get_run( + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), + request_options=None, + ) + except Error as e: + handle_fern_exception(e) + raise e + + def get_dataset_runs( + self, + *, + dataset_name: str, + page: Optional[int] = None, + limit: Optional[int] = None, + ) -> PaginatedDatasetRuns: + """Fetch all runs for a dataset. + + Args: + dataset_name (str): The name of the dataset. + page (Optional[int]): Page number, starts at 1. + limit (Optional[int]): Limit of items per page. + + Returns: + PaginatedDatasetRuns: Paginated list of dataset runs. + """ + try: + return self.api.datasets.get_runs( + dataset_name=self._url_encode(dataset_name), + page=page, + limit=limit, + request_options=None, + ) + except Error as e: + handle_fern_exception(e) + raise e + + def delete_dataset_run( + self, *, dataset_name: str, run_name: str + ) -> DeleteDatasetRunResponse: + """Delete a dataset run and all its run items. This action is irreversible. + + Args: + dataset_name (str): The name of the dataset. + run_name (str): The name of the run. + + Returns: + DeleteDatasetRunResponse: Confirmation of deletion. + """ + try: + return self.api.datasets.delete_run( + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), + request_options=None, + ) + except Error as e: + handle_fern_exception(e) + raise e + def run_experiment( self, *, diff --git a/tests/test_datasets.py b/tests/test_datasets.py index c1b81868d..051dcfbf6 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -220,7 +220,7 @@ def test_get_dataset_runs(): langfuse.flush() time.sleep(1) # Give API time to process - runs = langfuse.api.datasets.get_runs(dataset_name) + runs = langfuse.get_dataset_runs(dataset_name=dataset_name) assert len(runs.data) == 2 assert runs.data[0].name == run_name_2 @@ -419,3 +419,111 @@ def execute_dataset_item(item, run_name): assert "args" in trace.input assert trace.input["args"][0] == expected_input assert trace.output == expected_input + + +def test_get_dataset_with_folder_name(): + """Test that get_dataset works with folder-format names containing slashes.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name (folder format) + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Fetch the dataset using the wrapper method + dataset = langfuse.get_dataset(folder_name) + assert dataset.name == folder_name + assert "/" in dataset.name # Verify slashes are preserved + + +def test_get_dataset_runs_with_folder_name(): + """Test that get_dataset_runs works with folder-format dataset names.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Create a dataset item + langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) + dataset = langfuse.get_dataset(folder_name) + assert len(dataset.items) == 1 + + # Create a run + run_name = f"run-{create_uuid()[:8]}" + for item in dataset.items: + with item.run(run_name=run_name): + pass + + langfuse.flush() + time.sleep(1) # Give API time to process + + # Fetch runs using the new wrapper method + runs = langfuse.get_dataset_runs(dataset_name=folder_name) + assert len(runs.data) == 1 + assert runs.data[0].name == run_name + + +def test_get_dataset_run_with_folder_names(): + """Test that get_dataset_run works with folder-format dataset and run names.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Create a dataset item + langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) + dataset = langfuse.get_dataset(folder_name) + assert len(dataset.items) == 1 + + # Create a run with slashes in the name + run_name = f"run/nested/{create_uuid()[:8]}" + for item in dataset.items: + with item.run(run_name=run_name, run_metadata={"key": "value"}): + pass + + langfuse.flush() + time.sleep(1) # Give API time to process + + # Fetch the specific run using the new wrapper method + run = langfuse.get_dataset_run(dataset_name=folder_name, run_name=run_name) + assert run.name == run_name + assert run.dataset_name == folder_name + assert run.metadata == {"key": "value"} + assert "/" in run_name # Verify slashes are preserved in run name + + +def test_delete_dataset_run_with_folder_names(): + """Test that delete_dataset_run works with folder-format dataset and run names.""" + langfuse = Langfuse(debug=False) + + # Create a dataset with slashes in the name + folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" + langfuse.create_dataset(name=folder_name) + + # Create a dataset item + langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) + dataset = langfuse.get_dataset(folder_name) + + # Create a run with slashes in the name + run_name = f"run/to/delete/{create_uuid()[:8]}" + for item in dataset.items: + with item.run(run_name=run_name): + pass + + langfuse.flush() + time.sleep(1) # Give API time to process + + # Verify the run exists + runs_before = langfuse.get_dataset_runs(dataset_name=folder_name) + assert len(runs_before.data) == 1 + + # Delete the run using the new wrapper method + result = langfuse.delete_dataset_run(dataset_name=folder_name, run_name=run_name) + assert result.message is not None + + time.sleep(1) # Give API time to process deletion + + # Verify the run is deleted + runs_after = langfuse.get_dataset_runs(dataset_name=folder_name) + assert len(runs_after.data) == 0 From f2d5da42fff95713f7eeaa583475c19d7eb6a27c Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 13 Jan 2026 14:17:18 +0000 Subject: [PATCH 157/296] chore: release v3.12.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 680fafd8d..60d1683c8 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.11.2" +__version__ = "3.12.0" diff --git a/pyproject.toml b/pyproject.toml index f5822d633..e744219ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.11.2" +version = "3.12.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 6bf4010ac52df63617399db369ebe0c3eaa1ec77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:55:23 +0200 Subject: [PATCH 158/296] chore(deps-dev): bump virtualenv from 20.35.3 to 20.36.1 (#1499) Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.35.3 to 20.36.1. - [Release notes](https://github.com/pypa/virtualenv/releases) - [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/virtualenv/compare/20.35.3...20.36.1) --- updated-dependencies: - dependency-name: virtualenv dependency-version: 20.36.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 76c603e18..5bdf471de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2501,19 +2501,19 @@ files = [ [[package]] name = "virtualenv" -version = "20.35.3" +version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a"}, - {file = "virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44"}, + {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, + {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" +filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} From 4caab4b35fc2acb373bcd873b7363e3f42889cb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:56:01 +0200 Subject: [PATCH 159/296] chore(deps-dev): bump filelock from 3.20.1 to 3.20.3 (#1500) Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.20.1 to 3.20.3. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.20.1...3.20.3) --- updated-dependencies: - dependency-name: filelock dependency-version: 3.20.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5bdf471de..e82a1fc8e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -326,14 +326,14 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"}, - {file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"}, + {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, + {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] [[package]] From 32ee150454d3f6af43b035778b56cbf0fb1b110d Mon Sep 17 00:00:00 2001 From: Doniyor Aliyev Date: Mon, 19 Jan 2026 21:11:26 +0500 Subject: [PATCH 160/296] fix(client): add metadata parameter to score_current_span and score_current_trace (#1501) Added optional metadata parameter to both score_current_span and score_current_trace methods to allow attaching additional context to scores. This brings these convenience methods to feature parity with the underlying create_score method which already supports metadata. Changes: - Added metadata parameter to all overloads of score_current_span - Added metadata parameter to all overloads of score_current_trace - Updated docstrings with metadata parameter documentation - Updated examples to demonstrate metadata usage - Passed metadata parameter through to create_score calls --- langfuse/_client/client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 7ee2f962f..ded3103e5 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2108,6 +2108,7 @@ def score_current_span( data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + metadata: Optional[Any] = None, ) -> None: ... @overload @@ -2120,6 +2121,7 @@ def score_current_span( data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, + metadata: Optional[Any] = None, ) -> None: ... def score_current_span( @@ -2131,6 +2133,7 @@ def score_current_span( data_type: Optional[ScoreDataType] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + metadata: Optional[Any] = None, ) -> None: """Create a score for the current active span. @@ -2144,6 +2147,7 @@ def score_current_span( data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse + metadata: Optional metadata to be attached to the score Example: ```python @@ -2157,7 +2161,8 @@ def score_current_span( name="relevance", value=0.85, data_type="NUMERIC", - comment="Mostly relevant but contains some tangential information" + comment="Mostly relevant but contains some tangential information", + metadata={"model": "gpt-4", "prompt_version": "v2"} ) ``` """ @@ -2180,6 +2185,7 @@ def score_current_span( data_type=cast(Literal["CATEGORICAL"], data_type), comment=comment, config_id=config_id, + metadata=metadata, ) @overload @@ -2192,6 +2198,7 @@ def score_current_trace( data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + metadata: Optional[Any] = None, ) -> None: ... @overload @@ -2204,6 +2211,7 @@ def score_current_trace( data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, + metadata: Optional[Any] = None, ) -> None: ... def score_current_trace( @@ -2215,6 +2223,7 @@ def score_current_trace( data_type: Optional[ScoreDataType] = None, comment: Optional[str] = None, config_id: Optional[str] = None, + metadata: Optional[Any] = None, ) -> None: """Create a score for the current trace. @@ -2229,6 +2238,7 @@ def score_current_trace( data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse + metadata: Optional metadata to be attached to the score Example: ```python @@ -2242,7 +2252,8 @@ def score_current_trace( name="overall_quality", value=0.95, data_type="NUMERIC", - comment="High quality end-to-end response" + comment="High quality end-to-end response", + metadata={"evaluator": "gpt-4", "criteria": "comprehensive"} ) ``` """ @@ -2263,6 +2274,7 @@ def score_current_trace( data_type=cast(Literal["CATEGORICAL"], data_type), comment=comment, config_id=config_id, + metadata=metadata, ) def flush(self) -> None: From 26a8589b7f06b1ed7ccdbf9116649bac07d158c8 Mon Sep 17 00:00:00 2001 From: James Idzik <60987954+jdidzik@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:56:18 -0500 Subject: [PATCH 161/296] fix: skip trace url generation when tracing is disabled (#1503) --- langfuse/_client/client.py | 5 ++++- tests/test_core_sdk.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ded3103e5..7c22d9b90 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2429,8 +2429,11 @@ def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]: send_notification(f"Review needed for trace: {specific_trace_url}") ``` """ - project_id = self._get_project_id() final_trace_id = trace_id or self.get_current_trace_id() + if not final_trace_id: + return None + + project_id = self._get_project_id() return ( f"{self._base_url}/project/{project_id}/traces/{final_trace_id}" diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index b2661eef5..2888c0554 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -1976,6 +1976,19 @@ def test_generate_trace_id(): assert trace_url == f"http://localhost:3000/project/{project_id}/traces/{trace_id}" +def test_generate_trace_url_client_disabled(): + langfuse = Langfuse(tracing_enabled=False) + + with langfuse.start_as_current_span( + name="test-span", + ): + # The trace URL should be None because the client is disabled + trace_url = langfuse.get_trace_url() + assert trace_url is None + + langfuse.flush() + + def test_start_as_current_observation_types(): """Test creating different observation types using start_as_current_observation.""" langfuse = Langfuse() From 2cd639f6bea3c0afac3dd9f1cfe295dec1a723a8 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:09:56 +0200 Subject: [PATCH 162/296] fix(batch-evaluation): pass trace ID for score creation if scope = observations (#1504) --- langfuse/batch_evaluation.py | 11 +++++++++++ tests/test_batch_evaluation.py | 28 +++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 35e1ea938..4a360773a 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -20,6 +20,7 @@ Protocol, Tuple, Union, + cast, ) from langfuse.api.resources.commons.types import ( @@ -1220,6 +1221,9 @@ async def _process_batch_evaluation_item( self._create_score_for_scope( scope=scope, item_id=item_id, + trace_id=cast(ObservationsView, item).trace_id + if scope == "observations" + else None, evaluation=evaluation, additional_metadata=metadata, ) @@ -1242,6 +1246,9 @@ async def _process_batch_evaluation_item( self._create_score_for_scope( scope=scope, item_id=item_id, + trace_id=cast(ObservationsView, item).trace_id + if scope == "observations" + else None, evaluation=composite_eval, additional_metadata=metadata, ) @@ -1361,8 +1368,10 @@ async def _run_composite_evaluator( def _create_score_for_scope( self, + *, scope: str, item_id: str, + trace_id: Optional[str] = None, evaluation: Evaluation, additional_metadata: Optional[Dict[str, Any]], ) -> None: @@ -1371,6 +1380,7 @@ def _create_score_for_scope( Args: scope: The type of entity ("traces", "observations"). item_id: The ID of the entity. + trace_id: The trace ID of the entity; required if scope=observations evaluation: The evaluation result to create a score from. additional_metadata: Additional metadata to merge with evaluation metadata. """ @@ -1393,6 +1403,7 @@ def _create_score_for_scope( elif scope == "observations": self.client.create_score( observation_id=item_id, + trace_id=trace_id, name=evaluation.name, value=evaluation.value, # type: ignore comment=evaluation.comment, diff --git a/tests/test_batch_evaluation.py b/tests/test_batch_evaluation.py index 9bf18348d..448172a55 100644 --- a/tests/test_batch_evaluation.py +++ b/tests/test_batch_evaluation.py @@ -25,7 +25,7 @@ # ============================================================================ -pytestmark = pytest.mark.skip(reason="Github CI runner overwhelmed by score volume") +# pytestmark = pytest.mark.skip(reason="Github CI runner overwhelmed by score volume") @pytest.fixture @@ -67,6 +67,32 @@ def simple_evaluator(*, input, output, expected_output=None, metadata=None, **kw # ============================================================================ +def test_run_batched_evaluation_on_observations_basic(langfuse_client): + """Test basic batch evaluation on traces.""" + result = langfuse_client.run_batched_evaluation( + scope="observations", + mapper=simple_trace_mapper, + evaluators=[simple_evaluator], + max_items=1, + verbose=True, + ) + + # Validate result structure + assert isinstance(result, BatchEvaluationResult) + assert result.total_items_fetched >= 0 + assert result.total_items_processed >= 0 + assert result.total_scores_created >= 0 + assert result.completed is True + assert isinstance(result.duration_seconds, float) + assert result.duration_seconds > 0 + + # Verify evaluator stats + assert len(result.evaluator_stats) == 1 + stats = result.evaluator_stats[0] + assert isinstance(stats, EvaluatorStats) + assert stats.name == "simple_evaluator" + + def test_run_batched_evaluation_on_traces_basic(langfuse_client): """Test basic batch evaluation on traces.""" result = langfuse_client.run_batched_evaluation( From cf653e94e61cc9d4c988c10ad8a669539f4a31a6 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 27 Jan 2026 06:11:11 +0000 Subject: [PATCH 163/296] chore: release v3.12.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 60d1683c8..408800ec0 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.12.0" +__version__ = "3.12.1" diff --git a/pyproject.toml b/pyproject.toml index e744219ff..ba00719fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.12.0" +version = "3.12.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From d11155e5492b401db227d87e32b587ad31bab7e9 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 28 Jan 2026 18:15:12 +0200 Subject: [PATCH 164/296] docs: Evaluation score cannot be None --- langfuse/experiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/experiment.py b/langfuse/experiment.py index 44c0bb859..cc5a9d8f7 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -124,7 +124,7 @@ class Evaluation: def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): if not expected_output: - return Evaluation(name="accuracy", value=None, comment="No expected output") + return Evaluation(name="accuracy", value=0, comment="No expected output") is_correct = output.strip().lower() == expected_output.strip().lower() return Evaluation( @@ -170,7 +170,7 @@ def external_api_evaluator(*, input, output, **kwargs): except Exception as e: return Evaluation( name="external_score", - value=None, + value=0, comment=f"API unavailable: {e}", metadata={"error": str(e), "retry_count": 3} ) From 0fd6338bba6abef3010f41aa7ac9a9c2638df619 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:51:32 +0200 Subject: [PATCH 165/296] feat(batch-evaluation): allow passing fields param for efficient trace fetching (#1502) * feat(batch-evaluation): allow passing fields param for efficient trace fetching * push --- langfuse/_client/client.py | 5 ++++- langfuse/batch_evaluation.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 7c22d9b90..ce7d7437d 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -79,12 +79,12 @@ from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache from langfuse.api.resources.commons.errors.error import Error +from langfuse.api.resources.commons.errors.not_found_error import NotFoundError from langfuse.api.resources.commons.types import DatasetRunWithItems from langfuse.api.resources.datasets.types import ( DeleteDatasetRunResponse, PaginatedDatasetRuns, ) -from langfuse.api.resources.commons.errors.not_found_error import NotFoundError from langfuse.api.resources.ingestion.types.score_body import ScoreBody from langfuse.api.resources.prompts.types import ( CreatePromptRequest_Chat, @@ -3096,6 +3096,7 @@ def run_batched_evaluation( mapper: MapperFunction, filter: Optional[str] = None, fetch_batch_size: int = 50, + fetch_trace_fields: Optional[str] = None, max_items: Optional[int] = None, max_retries: int = 3, evaluators: List[EvaluatorFunction], @@ -3138,6 +3139,7 @@ def run_batched_evaluation( Default: None (fetches all items). fetch_batch_size: Number of items to fetch per API call and hold in memory. Larger values may be faster but use more memory. Default: 50. + fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'. max_items: Maximum total number of items to process. If None, processes all items matching the filter. Useful for testing or limiting evaluation runs. Default: None (process all). @@ -3306,6 +3308,7 @@ def composite_evaluator(*, item, evaluations): evaluators=evaluators, filter=filter, fetch_batch_size=fetch_batch_size, + fetch_trace_fields=fetch_trace_fields, max_items=max_items, max_concurrency=max_concurrency, composite_evaluator=composite_evaluator, diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 4a360773a..7783955dd 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -847,6 +847,7 @@ async def run_async( evaluators: List[EvaluatorFunction], filter: Optional[str] = None, fetch_batch_size: int = 50, + fetch_trace_fields: Optional[str] = None, max_items: Optional[int] = None, max_concurrency: int = 50, composite_evaluator: Optional[CompositeEvaluatorFunction] = None, @@ -867,6 +868,7 @@ async def run_async( evaluators: List of evaluation functions to run on each item. filter: JSON filter string for querying items. fetch_batch_size: Number of items to fetch per API call. + fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'. max_items: Maximum number of items to process (None = all). max_concurrency: Maximum number of concurrent evaluations. composite_evaluator: Optional function to create composite scores. @@ -913,6 +915,8 @@ async def run_async( if verbose: self._log.info(f"Starting batch evaluation on {scope}") + if scope == "traces" and fetch_trace_fields: + self._log.info(f"Fetching trace fields: {fetch_trace_fields}") if resume_from: self._log.info( f"Resuming from {resume_from.last_processed_timestamp} " @@ -936,6 +940,7 @@ async def run_async( page=page, limit=fetch_batch_size, max_retries=max_retries, + fields=fetch_trace_fields, ) except Exception as e: # Failed after max_retries - create resume token and return @@ -1115,6 +1120,7 @@ async def _fetch_batch_with_retry( page: int, limit: int, max_retries: int, + fields: Optional[str], ) -> List[Union[TraceWithFullDetails, ObservationsView]]: """Fetch a batch of items with retry logic. @@ -1125,6 +1131,7 @@ async def _fetch_batch_with_retry( limit: Number of items per page. max_retries: Maximum number of retry attempts. verbose: Whether to log retry attempts. + fields: Trace fields to fetch Returns: List of items from the API. @@ -1138,6 +1145,7 @@ async def _fetch_batch_with_retry( limit=limit, filter=filter, request_options={"max_retries": max_retries}, + fields=fields, ) # type: ignore return list(response.data) # type: ignore elif scope == "observations": From 54fff05d4985e8e2836f41999380d90fa2fce5b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:13:19 +0200 Subject: [PATCH 166/296] chore(deps): bump protobuf from 6.33.0 to 6.33.5 (#1507) Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 6.33.0 to 6.33.5. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Commits](https://github.com/protocolbuffers/protobuf/commits) --- updated-dependencies: - dependency-name: protobuf dependency-version: 6.33.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index e82a1fc8e..fbd148185 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1509,22 +1509,22 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "6.33.0" +version = "6.33.5" description = "" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035"}, - {file = "protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee"}, - {file = "protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455"}, - {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90"}, - {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298"}, - {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef"}, - {file = "protobuf-6.33.0-cp39-cp39-win32.whl", hash = "sha256:cd33a8e38ea3e39df66e1bbc462b076d6e5ba3a4ebbde58219d777223a7873d3"}, - {file = "protobuf-6.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:c963e86c3655af3a917962c9619e1a6b9670540351d7af9439d06064e3317cc9"}, - {file = "protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995"}, - {file = "protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954"}, + {file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"}, + {file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"}, + {file = "protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5"}, + {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190"}, + {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd"}, + {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0"}, + {file = "protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c"}, + {file = "protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a"}, + {file = "protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02"}, + {file = "protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c"}, ] [[package]] From c8798aca6ba6968f5bddef592b267b11cee7badf Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 6 Feb 2026 19:54:01 +0000 Subject: [PATCH 167/296] chore: release v3.13.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 408800ec0..8c275fdf9 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.12.1" +__version__ = "3.13.0" diff --git a/pyproject.toml b/pyproject.toml index ba00719fc..40bd56e67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.12.1" +version = "3.13.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From d55d52c0bc17c3c08c0a6649e3e91f1f887e6119 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 9 Feb 2026 11:16:14 +0100 Subject: [PATCH 168/296] feat(api): update API spec from langfuse/langfuse 966662e (#1508) * feat(api): update API spec from langfuse/langfuse 966662e * chore: support dataset versioning via SDK * chore: support dataset versioning via SDK --------- Co-authored-by: langfuse-bot Co-authored-by: Marlies Mayerhofer <74332854+marliessophie@users.noreply.github.com> --- langfuse/_client/client.py | 10 ++- langfuse/api/__init__.py | 8 +++ langfuse/api/reference.md | 15 ++++- langfuse/api/resources/__init__.py | 8 +++ langfuse/api/resources/commons/__init__.py | 4 ++ .../api/resources/commons/types/__init__.py | 11 ++- .../commons/types/correction_score.py | 53 +++++++++++++++ langfuse/api/resources/commons/types/score.py | 67 ++++++++++++++++++- .../api/resources/dataset_items/client.py | 22 +++++- .../types/create_dataset_run_item_request.py | 10 +++ langfuse/api/resources/score_v_2/__init__.py | 4 ++ .../api/resources/score_v_2/types/__init__.py | 4 ++ .../types/get_scores_response_data.py | 67 +++++++++++++++++++ .../get_scores_response_data_correction.py | 46 +++++++++++++ tests/test_datasets.py | 42 ++++++++++++ 15 files changed, 365 insertions(+), 6 deletions(-) create mode 100644 langfuse/api/resources/commons/types/correction_score.py create mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ce7d7437d..f7ee28e36 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2442,13 +2442,20 @@ def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]: ) def get_dataset( - self, name: str, *, fetch_items_page_size: Optional[int] = 50 + self, + name: str, + *, + fetch_items_page_size: Optional[int] = 50, + version: Optional[datetime] = None, ) -> "DatasetClient": """Fetch a dataset by its name. Args: name (str): The name of the dataset to fetch. fetch_items_page_size (Optional[int]): All items of the dataset will be fetched in chunks of this size. Defaults to 50. + version (Optional[datetime]): Retrieve dataset items as they existed at this specific point in time (UTC). + If provided, returns the state of items at the specified UTC timestamp. + If not provided, returns the latest version. Must be a timezone-aware datetime object in UTC. Returns: DatasetClient: The dataset with the given name. @@ -2465,6 +2472,7 @@ def get_dataset( dataset_name=self._url_encode(name, is_url_param=True), page=page, limit=fetch_items_page_size, + version=version, ) dataset_items.extend(new_items.data) diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 835bdfefa..d1a6414ed 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -36,6 +36,7 @@ Comment, CommentObjectType, ConfigCategory, + CorrectionScore, CreateAnnotationQueueAssignmentResponse, CreateAnnotationQueueItemRequest, CreateAnnotationQueueRequest, @@ -85,9 +86,11 @@ GetScoresResponseData, GetScoresResponseDataBoolean, GetScoresResponseDataCategorical, + GetScoresResponseDataCorrection, GetScoresResponseDataNumeric, GetScoresResponseData_Boolean, GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, GetScoresResponseData_Numeric, GetScoresResponseTraceData, HealthResponse, @@ -199,6 +202,7 @@ ScoreV1_Numeric, Score_Boolean, Score_Categorical, + Score_Correction, Score_Numeric, SdkLogBody, SdkLogEvent, @@ -293,6 +297,7 @@ "Comment", "CommentObjectType", "ConfigCategory", + "CorrectionScore", "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", "CreateAnnotationQueueRequest", @@ -342,9 +347,11 @@ "GetScoresResponseData", "GetScoresResponseDataBoolean", "GetScoresResponseDataCategorical", + "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", + "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", "GetScoresResponseTraceData", "HealthResponse", @@ -456,6 +463,7 @@ "ScoreV1_Numeric", "Score_Boolean", "Score_Categorical", + "Score_Correction", "Score_Numeric", "SdkLogBody", "SdkLogEvent", diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 19870d547..5f6371b51 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -1519,7 +1519,8 @@ client.dataset_items.get(
-Get dataset items +Get dataset items. Optionally specify a version to get the items as they existed at that point in time. +Note: If version parameter is provided, datasetName must also be provided.
@@ -1584,6 +1585,18 @@ client.dataset_items.list()
+**version:** `typing.Optional[dt.datetime]` + +ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). +If provided, returns state of dataset at this timestamp. +If not provided, returns the latest version. Requires datasetName to be specified. + +
+
+ +
+
+ **page:** `typing.Optional[int]` — page number, starts at 1
diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py index 55c4e012a..0de0a56a5 100644 --- a/langfuse/api/resources/__init__.py +++ b/langfuse/api/resources/__init__.py @@ -67,6 +67,7 @@ Comment, CommentObjectType, ConfigCategory, + CorrectionScore, CreateScoreValue, Dataset, DatasetItem, @@ -101,6 +102,7 @@ ScoreV1_Numeric, Score_Boolean, Score_Categorical, + Score_Correction, Score_Numeric, Session, SessionWithTraces, @@ -268,9 +270,11 @@ GetScoresResponseData, GetScoresResponseDataBoolean, GetScoresResponseDataCategorical, + GetScoresResponseDataCorrection, GetScoresResponseDataNumeric, GetScoresResponseData_Boolean, GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, GetScoresResponseData_Numeric, GetScoresResponseTraceData, ) @@ -313,6 +317,7 @@ "Comment", "CommentObjectType", "ConfigCategory", + "CorrectionScore", "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", "CreateAnnotationQueueRequest", @@ -362,9 +367,11 @@ "GetScoresResponseData", "GetScoresResponseDataBoolean", "GetScoresResponseDataCategorical", + "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", + "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", "GetScoresResponseTraceData", "HealthResponse", @@ -476,6 +483,7 @@ "ScoreV1_Numeric", "Score_Boolean", "Score_Categorical", + "Score_Correction", "Score_Numeric", "SdkLogBody", "SdkLogEvent", diff --git a/langfuse/api/resources/commons/__init__.py b/langfuse/api/resources/commons/__init__.py index 9e522548e..7105b22c5 100644 --- a/langfuse/api/resources/commons/__init__.py +++ b/langfuse/api/resources/commons/__init__.py @@ -10,6 +10,7 @@ Comment, CommentObjectType, ConfigCategory, + CorrectionScore, CreateScoreValue, Dataset, DatasetItem, @@ -41,6 +42,7 @@ ScoreV1_Numeric, Score_Boolean, Score_Categorical, + Score_Correction, Score_Numeric, Session, SessionWithTraces, @@ -68,6 +70,7 @@ "Comment", "CommentObjectType", "ConfigCategory", + "CorrectionScore", "CreateScoreValue", "Dataset", "DatasetItem", @@ -102,6 +105,7 @@ "ScoreV1_Numeric", "Score_Boolean", "Score_Categorical", + "Score_Correction", "Score_Numeric", "Session", "SessionWithTraces", diff --git a/langfuse/api/resources/commons/types/__init__.py b/langfuse/api/resources/commons/types/__init__.py index b9063f3fb..df87680b7 100644 --- a/langfuse/api/resources/commons/types/__init__.py +++ b/langfuse/api/resources/commons/types/__init__.py @@ -9,6 +9,7 @@ from .comment import Comment from .comment_object_type import CommentObjectType from .config_category import ConfigCategory +from .correction_score import CorrectionScore from .create_score_value import CreateScoreValue from .dataset import Dataset from .dataset_item import DatasetItem @@ -29,7 +30,13 @@ from .pricing_tier_condition import PricingTierCondition from .pricing_tier_input import PricingTierInput from .pricing_tier_operator import PricingTierOperator -from .score import Score, Score_Boolean, Score_Categorical, Score_Numeric +from .score import ( + Score, + Score_Boolean, + Score_Categorical, + Score_Correction, + Score_Numeric, +) from .score_config import ScoreConfig from .score_config_data_type import ScoreConfigDataType from .score_data_type import ScoreDataType @@ -52,6 +59,7 @@ "Comment", "CommentObjectType", "ConfigCategory", + "CorrectionScore", "CreateScoreValue", "Dataset", "DatasetItem", @@ -83,6 +91,7 @@ "ScoreV1_Numeric", "Score_Boolean", "Score_Categorical", + "Score_Correction", "Score_Numeric", "Session", "SessionWithTraces", diff --git a/langfuse/api/resources/commons/types/correction_score.py b/langfuse/api/resources/commons/types/correction_score.py new file mode 100644 index 000000000..26abeae49 --- /dev/null +++ b/langfuse/api/resources/commons/types/correction_score.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from .base_score import BaseScore + + +class CorrectionScore(BaseScore): + value: float = pydantic_v1.Field() + """ + The numeric value of the score. Always 0 for correction scores. + """ + + string_value: str = pydantic_v1.Field(alias="stringValue") + """ + The string representation of the correction content + """ + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/score.py b/langfuse/api/resources/commons/types/score.py index 8d54b6575..dab6eee43 100644 --- a/langfuse/api/resources/commons/types/score.py +++ b/langfuse/api/resources/commons/types/score.py @@ -204,4 +204,69 @@ class Config: json_encoders = {dt.datetime: serialize_datetime} -Score = typing.Union[Score_Numeric, Score_Categorical, Score_Boolean] +class Score_Correction(pydantic_v1.BaseModel): + value: float + string_value: str = pydantic_v1.Field(alias="stringValue") + id: str + trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) + session_id: typing.Optional[str] = pydantic_v1.Field( + alias="sessionId", default=None + ) + observation_id: typing.Optional[str] = pydantic_v1.Field( + alias="observationId", default=None + ) + dataset_run_id: typing.Optional[str] = pydantic_v1.Field( + alias="datasetRunId", default=None + ) + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") + updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") + author_user_id: typing.Optional[str] = pydantic_v1.Field( + alias="authorUserId", default=None + ) + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) + queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) + environment: str + data_type: typing.Literal["CORRECTION"] = pydantic_v1.Field( + alias="dataType", default="CORRECTION" + ) + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} + + +Score = typing.Union[Score_Numeric, Score_Categorical, Score_Boolean, Score_Correction] diff --git a/langfuse/api/resources/dataset_items/client.py b/langfuse/api/resources/dataset_items/client.py index 8ece3a790..f557c5eab 100644 --- a/langfuse/api/resources/dataset_items/client.py +++ b/langfuse/api/resources/dataset_items/client.py @@ -1,10 +1,12 @@ # This file was auto-generated by Fern from our API Definition. +import datetime as dt import typing from json.decoder import JSONDecodeError from ...core.api_error import ApiError from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.datetime_utils import serialize_datetime from ...core.jsonable_encoder import jsonable_encoder from ...core.pydantic_utilities import pydantic_v1 from ...core.request_options import RequestOptions @@ -168,12 +170,14 @@ def list( dataset_name: typing.Optional[str] = None, source_trace_id: typing.Optional[str] = None, source_observation_id: typing.Optional[str] = None, + version: typing.Optional[dt.datetime] = None, page: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> PaginatedDatasetItems: """ - Get dataset items + Get dataset items. Optionally specify a version to get the items as they existed at that point in time. + Note: If version parameter is provided, datasetName must also be provided. Parameters ---------- @@ -183,6 +187,11 @@ def list( source_observation_id : typing.Optional[str] + version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + If provided, returns state of dataset at this timestamp. + If not provided, returns the latest version. Requires datasetName to be specified. + page : typing.Optional[int] page number, starts at 1 @@ -217,6 +226,7 @@ def list( "datasetName": dataset_name, "sourceTraceId": source_trace_id, "sourceObservationId": source_observation_id, + "version": serialize_datetime(version) if version is not None else None, "page": page, "limit": limit, }, @@ -477,12 +487,14 @@ async def list( dataset_name: typing.Optional[str] = None, source_trace_id: typing.Optional[str] = None, source_observation_id: typing.Optional[str] = None, + version: typing.Optional[dt.datetime] = None, page: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, ) -> PaginatedDatasetItems: """ - Get dataset items + Get dataset items. Optionally specify a version to get the items as they existed at that point in time. + Note: If version parameter is provided, datasetName must also be provided. Parameters ---------- @@ -492,6 +504,11 @@ async def list( source_observation_id : typing.Optional[str] + version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + If provided, returns state of dataset at this timestamp. + If not provided, returns the latest version. Requires datasetName to be specified. + page : typing.Optional[int] page number, starts at 1 @@ -534,6 +551,7 @@ async def main() -> None: "datasetName": dataset_name, "sourceTraceId": source_trace_id, "sourceObservationId": source_observation_id, + "version": serialize_datetime(version) if version is not None else None, "page": page, "limit": limit, }, diff --git a/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py b/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py index 0a643b835..091f34e7e 100644 --- a/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py +++ b/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py @@ -30,6 +30,16 @@ class CreateDatasetRunItemRequest(pydantic_v1.BaseModel): traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. """ + dataset_version: typing.Optional[dt.datetime] = pydantic_v1.Field( + alias="datasetVersion", default=None + ) + """ + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + Specifies the dataset version to use for this experiment run. + If provided, the experiment will use dataset items as they existed at or before this timestamp. + If not provided, uses the latest version of dataset items. + """ + def json(self, **kwargs: typing.Any) -> str: kwargs_with_defaults: typing.Any = { "by_alias": True, diff --git a/langfuse/api/resources/score_v_2/__init__.py b/langfuse/api/resources/score_v_2/__init__.py index 40599eec1..4e333a693 100644 --- a/langfuse/api/resources/score_v_2/__init__.py +++ b/langfuse/api/resources/score_v_2/__init__.py @@ -5,9 +5,11 @@ GetScoresResponseData, GetScoresResponseDataBoolean, GetScoresResponseDataCategorical, + GetScoresResponseDataCorrection, GetScoresResponseDataNumeric, GetScoresResponseData_Boolean, GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, GetScoresResponseData_Numeric, GetScoresResponseTraceData, ) @@ -17,9 +19,11 @@ "GetScoresResponseData", "GetScoresResponseDataBoolean", "GetScoresResponseDataCategorical", + "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", + "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", "GetScoresResponseTraceData", ] diff --git a/langfuse/api/resources/score_v_2/types/__init__.py b/langfuse/api/resources/score_v_2/types/__init__.py index 480ed3406..d08e687ef 100644 --- a/langfuse/api/resources/score_v_2/types/__init__.py +++ b/langfuse/api/resources/score_v_2/types/__init__.py @@ -5,10 +5,12 @@ GetScoresResponseData, GetScoresResponseData_Boolean, GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, GetScoresResponseData_Numeric, ) from .get_scores_response_data_boolean import GetScoresResponseDataBoolean from .get_scores_response_data_categorical import GetScoresResponseDataCategorical +from .get_scores_response_data_correction import GetScoresResponseDataCorrection from .get_scores_response_data_numeric import GetScoresResponseDataNumeric from .get_scores_response_trace_data import GetScoresResponseTraceData @@ -17,9 +19,11 @@ "GetScoresResponseData", "GetScoresResponseDataBoolean", "GetScoresResponseDataCategorical", + "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", + "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", "GetScoresResponseTraceData", ] diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py index 965a01c80..4f73fbcae 100644 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py +++ b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py @@ -208,8 +208,75 @@ class Config: json_encoders = {dt.datetime: serialize_datetime} +class GetScoresResponseData_Correction(pydantic_v1.BaseModel): + trace: typing.Optional[GetScoresResponseTraceData] = None + value: float + string_value: str = pydantic_v1.Field(alias="stringValue") + id: str + trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) + session_id: typing.Optional[str] = pydantic_v1.Field( + alias="sessionId", default=None + ) + observation_id: typing.Optional[str] = pydantic_v1.Field( + alias="observationId", default=None + ) + dataset_run_id: typing.Optional[str] = pydantic_v1.Field( + alias="datasetRunId", default=None + ) + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") + updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") + author_user_id: typing.Optional[str] = pydantic_v1.Field( + alias="authorUserId", default=None + ) + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) + queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) + environment: str + data_type: typing.Literal["CORRECTION"] = pydantic_v1.Field( + alias="dataType", default="CORRECTION" + ) + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} + + GetScoresResponseData = typing.Union[ GetScoresResponseData_Numeric, GetScoresResponseData_Categorical, GetScoresResponseData_Boolean, + GetScoresResponseData_Correction, ] diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py new file mode 100644 index 000000000..0c59f29a8 --- /dev/null +++ b/langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ....core.datetime_utils import serialize_datetime +from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +from ...commons.types.correction_score import CorrectionScore +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseDataCorrection(CorrectionScore): + trace: typing.Optional[GetScoresResponseTraceData] = None + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + kwargs_with_defaults_exclude_none: typing.Any = { + "by_alias": True, + "exclude_none": True, + **kwargs, + } + + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), + super().dict(**kwargs_with_defaults_exclude_none), + ) + + class Config: + frozen = True + smart_union = True + allow_population_by_field_name = True + populate_by_name = True + extra = pydantic_v1.Extra.allow + json_encoders = {dt.datetime: serialize_datetime} diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 051dcfbf6..f86812138 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -1,5 +1,6 @@ import json import time +from datetime import timedelta from concurrent.futures import ThreadPoolExecutor from typing import Sequence @@ -527,3 +528,44 @@ def test_delete_dataset_run_with_folder_names(): # Verify the run is deleted runs_after = langfuse.get_dataset_runs(dataset_name=folder_name) assert len(runs_after.data) == 0 + + +def test_get_dataset_with_version(): + """Test that get_dataset correctly filters items by version timestamp.""" + + langfuse = Langfuse(debug=False) + + # Create dataset + name = create_uuid() + langfuse.create_dataset(name=name) + + # Create first item + item1 = langfuse.create_dataset_item(dataset_name=name, input={"version": "v1"}) + langfuse.flush() + time.sleep(3) # Ensure persistence + + # Fetch dataset to get the actual server-assigned timestamp of item1 + dataset_after_item1 = langfuse.get_dataset(name) + assert len(dataset_after_item1.items) == 1 + item1_created_at = dataset_after_item1.items[0].created_at + + # Use a timestamp 1 second after item1's actual creation time + query_timestamp = item1_created_at + timedelta(seconds=1) + time.sleep(3) # Ensure temporal separation + + # Create second item + langfuse.create_dataset_item(dataset_name=name, input={"version": "v2"}) + langfuse.flush() + time.sleep(3) # Ensure persistence + + # Fetch at the query_timestamp (should only return first item) + dataset = langfuse.get_dataset(name, version=query_timestamp) + + # Verify only first item is retrieved + assert len(dataset.items) == 1 + assert dataset.items[0].input == {"version": "v1"} + assert dataset.items[0].id == item1.id + + # Verify fetching without version returns both items (latest) + dataset_latest = langfuse.get_dataset(name) + assert len(dataset_latest.items) == 2 From 1ae292384f59b4ad556009c5925a0f40fcdc37bb Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 9 Feb 2026 12:20:35 +0000 Subject: [PATCH 169/296] chore: release v3.14.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 8c275fdf9..35892a926 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.13.0" +__version__ = "3.14.0" diff --git a/pyproject.toml b/pyproject.toml index 40bd56e67..7f8defb1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.13.0" +version = "3.14.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 317e7058fb7890438e41e5e9a8662d7e22799e11 Mon Sep 17 00:00:00 2001 From: marliessophie <74332854+marliessophie@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:27:42 +0100 Subject: [PATCH 170/296] feat(dataset-versioning): support running versioned experiments (#1517) * feat(dataset-versioning): support running versioned experiments * test: add dataset item creation in versioned experiment test --- langfuse/_client/client.py | 8 +++- langfuse/_client/datasets.py | 12 +++++- tests/test_datasets.py | 71 ++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index f7ee28e36..c8782c638 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2483,7 +2483,7 @@ def get_dataset( items = [DatasetItemClient(i, langfuse=self) for i in dataset_items] - return DatasetClient(dataset, items=items) + return DatasetClient(dataset, items=items, version=version) except Error as e: handle_fern_exception(e) @@ -2574,6 +2574,7 @@ def run_experiment( run_evaluators: List[RunEvaluatorFunction] = [], max_concurrency: int = 50, metadata: Optional[Dict[str, str]] = None, + _dataset_version: Optional[datetime] = None, ) -> ExperimentResult: """Run an experiment on a dataset with automatic tracing and evaluation. @@ -2751,6 +2752,7 @@ def average_accuracy(*, item_results, **kwargs): run_evaluators=run_evaluators or [], max_concurrency=max_concurrency, metadata=metadata, + dataset_version=_dataset_version, ), ), ) @@ -2768,6 +2770,7 @@ async def _run_experiment_async( run_evaluators: List[RunEvaluatorFunction], max_concurrency: int, metadata: Optional[Dict[str, Any]] = None, + dataset_version: Optional[datetime] = None, ) -> ExperimentResult: langfuse_logger.debug( f"Starting experiment '{name}' run '{run_name}' with {len(data)} items" @@ -2788,6 +2791,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult: run_name, description, metadata, + dataset_version, ) # Run all items concurrently @@ -2874,6 +2878,7 @@ async def _process_experiment_item( experiment_run_name: str, experiment_description: Optional[str], experiment_metadata: Optional[Dict[str, Any]] = None, + dataset_version: Optional[datetime] = None, ) -> ExperimentItemResult: span_name = "experiment-item-run" @@ -2925,6 +2930,7 @@ async def _process_experiment_item( datasetItemId=item.id, # type: ignore traceId=trace_id, observationId=span.id, + datasetVersion=dataset_version, ), ) diff --git a/langfuse/_client/datasets.py b/langfuse/_client/datasets.py index 0a9a0312c..4a698e0ab 100644 --- a/langfuse/_client/datasets.py +++ b/langfuse/_client/datasets.py @@ -155,7 +155,7 @@ class DatasetClient: created_at (datetime): Timestamp of dataset creation. updated_at (datetime): Timestamp of the last update to the dataset. items (List[DatasetItemClient]): List of dataset items associated with the dataset. - + version (Optional[datetime]): Timestamp of the dataset version. Example: Print the input of each dataset item in a dataset. ```python @@ -178,8 +178,14 @@ class DatasetClient: created_at: dt.datetime updated_at: dt.datetime items: List[DatasetItemClient] + version: Optional[dt.datetime] - def __init__(self, dataset: Dataset, items: List[DatasetItemClient]): + def __init__( + self, + dataset: Dataset, + items: List[DatasetItemClient], + version: Optional[dt.datetime] = None, + ): """Initialize the DatasetClient.""" self.id = dataset.id self.name = dataset.name @@ -189,6 +195,7 @@ def __init__(self, dataset: Dataset, items: List[DatasetItemClient]): self.created_at = dataset.created_at self.updated_at = dataset.updated_at self.items = items + self.version = version self._langfuse: Optional["Langfuse"] = None def _get_langfuse_client(self) -> Optional["Langfuse"]: @@ -421,4 +428,5 @@ def content_diversity(*, item_results, **kwargs): run_evaluators=run_evaluators, max_concurrency=max_concurrency, metadata=metadata, + _dataset_version=self.version, ) diff --git a/tests/test_datasets.py b/tests/test_datasets.py index f86812138..1a0cda63a 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -569,3 +569,74 @@ def test_get_dataset_with_version(): # Verify fetching without version returns both items (latest) dataset_latest = langfuse.get_dataset(name) assert len(dataset_latest.items) == 2 + + +def test_run_experiment_with_versioned_dataset(): + """Test that running an experiment on a versioned dataset works correctly.""" + from datetime import timedelta + import time + + langfuse = Langfuse(debug=False) + + # Create dataset + name = create_uuid() + langfuse.create_dataset(name=name) + + # Create first item + langfuse.create_dataset_item( + dataset_name=name, input={"question": "What is 2+2?"}, expected_output=4 + ) + langfuse.flush() + time.sleep(3) + + # Fetch dataset to get the actual server-assigned timestamp of item1 + dataset_after_item1 = langfuse.get_dataset(name) + assert len(dataset_after_item1.items) == 1 + item1_id = dataset_after_item1.items[0].id + item1_created_at = dataset_after_item1.items[0].created_at + + # Use a timestamp 1 second after item1's creation + version_timestamp = item1_created_at + timedelta(seconds=1) + time.sleep(3) + + # Update item1 after the version timestamp (this should not affect versioned query) + langfuse.create_dataset_item( + id=item1_id, + dataset_name=name, + input={"question": "What is 4+4?"}, + expected_output=8, + ) + langfuse.flush() + time.sleep(3) + + # Create second item (after version timestamp) + langfuse.create_dataset_item( + dataset_name=name, input={"question": "What is 3+3?"}, expected_output=6 + ) + langfuse.flush() + time.sleep(3) + + # Get versioned dataset (should only have first item with ORIGINAL state) + versioned_dataset = langfuse.get_dataset(name, version=version_timestamp) + assert len(versioned_dataset.items) == 1 + assert versioned_dataset.version == version_timestamp + # Verify it returns the ORIGINAL version of item1 (before the update) + assert versioned_dataset.items[0].input == {"question": "What is 2+2?"} + assert versioned_dataset.items[0].expected_output == 4 + assert versioned_dataset.items[0].id == item1_id + + # Run a simple experiment on the versioned dataset + def simple_task(*, item, **kwargs): + # Just return a static answer + return item.expected_output + + result = versioned_dataset.run_experiment( + name="Versioned Dataset Test", + description="Testing experiment with versioned dataset", + task=simple_task, + ) + + # Verify experiment ran successfully + assert result.name == "Versioned Dataset Test" + assert len(result.item_results) == 1 # Only one item in versioned dataset + assert result.item_results[0].output == 4 From 04f80f94e05bc5a705ed465c0c5bd2b5926d8eaa Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 9 Feb 2026 15:37:32 +0000 Subject: [PATCH 171/296] chore: release v3.14.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 35892a926..ea4c2d9d1 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.0" +__version__ = "3.14.1" diff --git a/pyproject.toml b/pyproject.toml index 7f8defb1c..2bfc5b96b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.0" +version = "3.14.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 0246093d8800f2374e84ac795edb8ce34b2f71de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:33:52 +0100 Subject: [PATCH 172/296] chore(deps-dev): bump langsmith from 0.4.37 to 0.6.3 (#1519) Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from 0.4.37 to 0.6.3. - [Release notes](https://github.com/langchain-ai/langsmith-sdk/releases) - [Commits](https://github.com/langchain-ai/langsmith-sdk/commits) --- updated-dependencies: - dependency-name: langsmith dependency-version: 0.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index fbd148185..8145f71f9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -816,23 +816,24 @@ orjson = ">=3.10.1" [[package]] name = "langsmith" -version = "0.4.37" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +version = "0.6.3" +description = "Client library to connect to the LangSmith Observability and Evaluation Platform." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "langsmith-0.4.37-py3-none-any.whl", hash = "sha256:e34a94ce7277646299e4703a0f6e2d2c43647a28e8b800bb7ef82fd87a0ec766"}, - {file = "langsmith-0.4.37.tar.gz", hash = "sha256:d9a0eb6dd93f89843ac982c9f92be93cf2bcabbe19957f362c547766c7366c71"}, + {file = "langsmith-0.6.3-py3-none-any.whl", hash = "sha256:44fdf8084165513e6bede9dda715e7b460b1b3f57ac69f2ca3f03afa911233ec"}, + {file = "langsmith-0.6.3.tar.gz", hash = "sha256:33246769c0bb24e2c17e0c34bb21931084437613cd37faf83bd0978a297b826f"}, ] [package.dependencies] httpx = ">=0.23.0,<1" orjson = {version = ">=3.9.14", markers = "platform_python_implementation != \"PyPy\""} packaging = ">=23.2" -pydantic = ">=1,<3" +pydantic = ">=2,<3" requests = ">=2.0.0" requests-toolbelt = ">=1.0.0" +uuid-utils = ">=0.12.0,<1.0" zstandard = ">=0.23.0" [package.extras] From c984097e2bf5f6e66ede43af5b96a004099bed77 Mon Sep 17 00:00:00 2001 From: CA_ShunsakuT Date: Wed, 11 Feb 2026 02:50:04 +0900 Subject: [PATCH 173/296] fix: evaluation docstring examples to use value=0 instead of value=None (#1520) docs: fix Evaluation docstring examples to use value=0 instead of value=None Update docstring examples in EvaluatorFunction and RunEvaluatorFunction protocols to use value=0 instead of value=None, matching the type definition which requires Union[int, float, str, bool] and does not allow None. This change aligns with commit d11155e5492b401db227d87e32b587ad31bab7e9 which established that Evaluation score values cannot be None. Changes: - Update accuracy_evaluator example in EvaluatorFunction docstring - Update llm_judge_evaluator error handling example - Update average_accuracy example in RunEvaluatorFunction docstring - Update accuracy_evaluator example in DatasetClient.run_experiment docstring --- langfuse/_client/datasets.py | 2 +- langfuse/experiment.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/langfuse/_client/datasets.py b/langfuse/_client/datasets.py index 4a698e0ab..7879fbea9 100644 --- a/langfuse/_client/datasets.py +++ b/langfuse/_client/datasets.py @@ -286,7 +286,7 @@ def answer_questions(*, item, **kwargs): def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): if not expected_output: - return {"name": "accuracy", "value": None, "comment": "No expected output"} + return {"name": "accuracy", "value": 0, "comment": "No expected output"} is_correct = output.strip().lower() == expected_output.strip().lower() return { diff --git a/langfuse/experiment.py b/langfuse/experiment.py index cc5a9d8f7..00c54fe74 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -719,7 +719,7 @@ def __call__( ```python def accuracy_evaluator(*, input, output, expected_output=None, **kwargs): if expected_output is None: - return {"name": "accuracy", "value": None, "comment": "No expected output"} + return {"name": "accuracy", "value": 0, "comment": "No expected output"} is_correct = output.strip().lower() == expected_output.strip().lower() return { @@ -773,7 +773,7 @@ async def llm_judge_evaluator(*, input, output, expected_output=None, **kwargs): except ValueError: return { "name": "llm_judge_quality", - "value": None, + "value": 0, "comment": "Could not parse LLM judge score" } ``` @@ -867,7 +867,7 @@ def average_accuracy(*, item_results, **kwargs): accuracy_values.append(evaluation.value) if not accuracy_values: - return {"name": "avg_accuracy", "value": None, "comment": "No accuracy evaluations found"} + return {"name": "avg_accuracy", "value": 0, "comment": "No accuracy evaluations found"} avg = sum(accuracy_values) / len(accuracy_values) return { From bd0fafc206232d5132750c98e2f0677d23496e61 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 11 Feb 2026 10:44:45 +0100 Subject: [PATCH 174/296] feat(api): update API spec from langfuse/langfuse 270f036 (#1521) Co-authored-by: langfuse-bot Co-authored-by: Valery Meleshkin --- langfuse/api/reference.md | 8 ++++++++ langfuse/api/resources/score_v_2/client.py | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md index 5f6371b51..8a8dad23c 100644 --- a/langfuse/api/reference.md +++ b/langfuse/api/reference.md @@ -7167,6 +7167,14 @@ client.score_v_2.get()
+**observation_id:** `typing.Optional[str]` — Comma-separated list of observation IDs to filter scores by. + +
+
+ +
+
+ **queue_id:** `typing.Optional[str]` — Retrieve only scores with a specific annotation queueId.
diff --git a/langfuse/api/resources/score_v_2/client.py b/langfuse/api/resources/score_v_2/client.py index 2a1295432..7372a7784 100644 --- a/langfuse/api/resources/score_v_2/client.py +++ b/langfuse/api/resources/score_v_2/client.py @@ -43,6 +43,7 @@ def get( session_id: typing.Optional[str] = None, dataset_run_id: typing.Optional[str] = None, trace_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, @@ -99,6 +100,9 @@ def get( trace_id : typing.Optional[str] Retrieve only scores with a specific traceId. + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter scores by. + queue_id : typing.Optional[str] Retrieve only scores with a specific annotation queueId. @@ -155,6 +159,7 @@ def get( "sessionId": session_id, "datasetRunId": dataset_run_id, "traceId": trace_id, + "observationId": observation_id, "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, @@ -276,6 +281,7 @@ async def get( session_id: typing.Optional[str] = None, dataset_run_id: typing.Optional[str] = None, trace_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, queue_id: typing.Optional[str] = None, data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, @@ -332,6 +338,9 @@ async def get( trace_id : typing.Optional[str] Retrieve only scores with a specific traceId. + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter scores by. + queue_id : typing.Optional[str] Retrieve only scores with a specific annotation queueId. @@ -396,6 +405,7 @@ async def main() -> None: "sessionId": session_id, "datasetRunId": dataset_run_id, "traceId": trace_id, + "observationId": observation_id, "queueId": queue_id, "dataType": data_type, "traceTags": trace_tags, From ab18ba11d1347bbcd389ad6ab5d852e95819a9ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:19:24 +0100 Subject: [PATCH 175/296] chore(deps-dev): bump langchain-core from 1.2.5 to 1.2.11 (#1523) Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 1.2.5 to 1.2.11. - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.5...langchain-core==1.2.11) --- updated-dependencies: - dependency-name: langchain-core dependency-version: 1.2.11 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8145f71f9..76b5321ef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -709,20 +709,20 @@ xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "1.2.5" +version = "1.2.11" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["dev"] files = [ - {file = "langchain_core-1.2.5-py3-none-any.whl", hash = "sha256:3255944ef4e21b2551facb319bfc426057a40247c0a05de5bd6f2fc021fbfa34"}, - {file = "langchain_core-1.2.5.tar.gz", hash = "sha256:d674f6df42f07e846859b9d3afe547cad333d6bf9763e92c88eb4f8aaedcd3cc"}, + {file = "langchain_core-1.2.11-py3-none-any.whl", hash = "sha256:ae11ceb8dda60d0b9d09e763116e592f1683327c17be5b715f350fd29aee65d3"}, + {file = "langchain_core-1.2.11.tar.gz", hash = "sha256:f164bb36602dd74a3a50c1334fca75309ad5ed95767acdfdbb9fa95ce28a1e01"}, ] [package.dependencies] jsonpatch = ">=1.33.0,<2.0.0" langsmith = ">=0.3.45,<1.0.0" -packaging = ">=23.2.0,<26.0.0" +packaging = ">=23.2.0" pydantic = ">=2.7.4,<3.0.0" pyyaml = ">=5.3.0,<7.0.0" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" From 81b58cdbbd0c39365cb56b77e88353f99bc8df48 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:26:37 +0100 Subject: [PATCH 176/296] fix(openai): parse usage details from response API response chunk (#1525) --- langfuse/openai.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 92d9346f4..c3fb7bb38 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -591,7 +591,8 @@ def _extract_streamed_response_api_response(chunks: Any) -> Any: for raw_chunk in chunks: chunk = raw_chunk.__dict__ if raw_response := chunk.get("response", None): - usage = chunk.get("usage", None) + usage = chunk.get("usage", None) or getattr(raw_response, "usage", None) + response = raw_response.__dict__ model = response.get("model") From c50b25693fbe59fdd661729336250716e4deed4e Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 16 Feb 2026 16:27:29 +0000 Subject: [PATCH 177/296] chore: release v3.14.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index ea4c2d9d1..c0abc1b08 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.1" +__version__ = "3.14.2" diff --git a/pyproject.toml b/pyproject.toml index 2bfc5b96b..419185e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.1" +version = "3.14.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 2246953016a67cee222054b417f11ab04b3f09fb Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:41:48 +0100 Subject: [PATCH 178/296] fix(openai): parse finish_reason from chat completion stream (#1526) --- langfuse/openai.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index c3fb7bb38..8ea396872 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -614,7 +614,7 @@ def _extract_streamed_response_api_response(chunks: Any) -> Any: def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: completion: Any = defaultdict(lambda: None) if resource.type == "chat" else "" - model, usage = None, None + model, usage, finish_reason = None, None, None for chunk in chunks: if _is_openai_v1(): @@ -630,6 +630,7 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: choice = choice.__dict__ if resource.type == "chat": delta = choice.get("delta", None) + finish_reason = choice.get("finish_reason", None) if _is_openai_v1(): delta = delta.__dict__ @@ -728,7 +729,7 @@ def get_response_for_chat() -> Any: model, get_response_for_chat() if resource.type == "chat" else completion, usage, - None, + {"finish_reason": finish_reason} if finish_reason is not None else None, ) From 5974a4697f0005513bf0e0102b5880a7d96df378 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 17 Feb 2026 17:43:13 +0000 Subject: [PATCH 179/296] chore: release v3.14.3 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index c0abc1b08..17706a2fc 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.2" +__version__ = "3.14.3" diff --git a/pyproject.toml b/pyproject.toml index 419185e53..0c7445e18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.2" +version = "3.14.3" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From ba28dae5d0718fb7c0126a450fec456595c7b742 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:57:46 +0100 Subject: [PATCH 180/296] fix(experiments): remove dataset version from run item request if None (#1529) --- langfuse/_client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index c8782c638..ccb5e3cbf 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2931,7 +2931,7 @@ async def _process_experiment_item( traceId=trace_id, observationId=span.id, datasetVersion=dataset_version, - ), + ).dict(exclude_none=True), ) dataset_run_id = dataset_run_item.dataset_run_id From 02418030ede06433ac2fbd31515ae126e09e110f Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 19 Feb 2026 11:02:57 +0000 Subject: [PATCH 181/296] chore: release v3.14.4 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 17706a2fc..b62dba2cc 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.3" +__version__ = "3.14.4" diff --git a/pyproject.toml b/pyproject.toml index 0c7445e18..271c152aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.3" +version = "3.14.4" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 0b2078e7a56984c9a455dba0f03c7f8a4a400cf5 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:40:30 +0100 Subject: [PATCH 182/296] feat(batch-evaluation): add trace tags and roll up scores to trace (#1530) --- langfuse/_client/client.py | 42 +++++++++++++- langfuse/_client/resource_manager.py | 23 ++++++++ langfuse/batch_evaluation.py | 82 ++++++++++++++++++++++++++-- 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index ccb5e3cbf..3787280c5 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -86,6 +86,7 @@ PaginatedDatasetRuns, ) from langfuse.api.resources.ingestion.types.score_body import ScoreBody +from langfuse.api.resources.ingestion.types.trace_body import TraceBody from langfuse.api.resources.prompts.types import ( CreatePromptRequest_Chat, CreatePromptRequest_Text, @@ -2098,6 +2099,39 @@ def create_score( f"Error creating score: Failed to process score event for trace_id={trace_id}, name={name}. Error: {e}" ) + def _create_trace_tags_via_ingestion( + self, + *, + trace_id: str, + tags: List[str], + ) -> None: + """Private helper to enqueue trace tag updates via ingestion API events.""" + if not self._tracing_enabled: + return + + if len(tags) == 0: + return + + try: + new_body = TraceBody( + id=trace_id, + tags=tags, + ) + + event = { + "id": self.create_trace_id(), + "type": "trace-create", + "timestamp": _get_timestamp(), + "body": new_body, + } + + if self._resources is not None: + self._resources.add_trace_task(event) + except Exception as e: + langfuse_logger.exception( + f"Error updating trace tags: Failed to process trace update event for trace_id={trace_id}. Error: {e}" + ) + @overload def score_current_span( self, @@ -3115,8 +3149,10 @@ def run_batched_evaluation( max_retries: int = 3, evaluators: List[EvaluatorFunction], composite_evaluator: Optional[CompositeEvaluatorFunction] = None, - max_concurrency: int = 50, + max_concurrency: int = 5, metadata: Optional[Dict[str, Any]] = None, + _add_observation_scores_to_trace: bool = False, + _additional_trace_tags: Optional[List[str]] = None, resume_from: Optional[BatchEvaluationResumeToken] = None, verbose: bool = False, ) -> BatchEvaluationResult: @@ -3158,7 +3194,7 @@ def run_batched_evaluation( items matching the filter. Useful for testing or limiting evaluation runs. Default: None (process all). max_concurrency: Maximum number of items to evaluate concurrently. Controls - parallelism and resource usage. Default: 50. + parallelism and resource usage. Default: 5. composite_evaluator: Optional function that creates a composite score from item-level evaluations. Receives the original item and its evaluations, returns a single Evaluation. Useful for weighted averages or combined metrics. @@ -3327,6 +3363,8 @@ def composite_evaluator(*, item, evaluations): max_concurrency=max_concurrency, composite_evaluator=composite_evaluator, metadata=metadata, + _add_observation_scores_to_trace=_add_observation_scores_to_trace, + _additional_trace_tags=_additional_trace_tags, max_retries=max_retries, verbose=verbose, resume_from=resume_from, diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 08c008234..e1b3aa7a9 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -351,6 +351,29 @@ def add_score_task(self, event: dict, *, force_sample: bool = False) -> None: return + def add_trace_task( + self, + event: dict, + ) -> None: + try: + langfuse_logger.debug( + f"Trace: Enqueuing event type={event['type']} for trace_id={event['body'].id}" + ) + self._score_ingestion_queue.put(event, block=False) + + except Full: + langfuse_logger.warning( + "System overload: Trace ingestion queue has reached capacity (100,000 items). Trace update will be dropped. Consider increasing flush frequency or decreasing event volume." + ) + + return + except Exception as e: + langfuse_logger.error( + f"Unexpected error: Failed to process trace event. The trace update will be dropped. Error details: {e}" + ) + + return + @property def tracer(self) -> Optional[Tracer]: return self._otel_tracer diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 7783955dd..6de480e67 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -18,6 +18,7 @@ List, Optional, Protocol, + Set, Tuple, Union, cast, @@ -849,9 +850,11 @@ async def run_async( fetch_batch_size: int = 50, fetch_trace_fields: Optional[str] = None, max_items: Optional[int] = None, - max_concurrency: int = 50, + max_concurrency: int = 5, composite_evaluator: Optional[CompositeEvaluatorFunction] = None, metadata: Optional[Dict[str, Any]] = None, + _add_observation_scores_to_trace: bool = False, + _additional_trace_tags: Optional[List[str]] = None, max_retries: int = 3, verbose: bool = False, resume_from: Optional[BatchEvaluationResumeToken] = None, @@ -873,6 +876,10 @@ async def run_async( max_concurrency: Maximum number of concurrent evaluations. composite_evaluator: Optional function to create composite scores. metadata: Metadata to add to all created scores. + _add_observation_scores_to_trace: Private option to duplicate + observation-level scores onto the parent trace. + _additional_trace_tags: Private option to add tags on traces via + ingestion trace-create events. max_retries: Maximum retries for failed batch fetches. verbose: If True, log progress to console. resume_from: Resume token from a previous failed run. @@ -903,6 +910,12 @@ async def run_async( # Handle resume token by modifying filter effective_filter = self._build_timestamp_filter(filter, resume_from) + normalized_additional_trace_tags = ( + self._dedupe_tags(_additional_trace_tags) + if _additional_trace_tags is not None + else [] + ) + updated_trace_ids: Set[str] = set() # Create semaphore for concurrency control semaphore = asyncio.Semaphore(max_concurrency) @@ -1011,6 +1024,7 @@ async def process_item( evaluators=evaluators, composite_evaluator=composite_evaluator, metadata=metadata, + _add_observation_scores_to_trace=_add_observation_scores_to_trace, evaluator_stats_dict=evaluator_stats_dict, ) return (item_id, result) @@ -1043,6 +1057,20 @@ async def process_item( # Store evaluations for this item item_evaluations[item_id] = evaluations + if normalized_additional_trace_tags: + trace_id = ( + item_id + if scope == "traces" + else cast(ObservationsView, item).trace_id + ) + + if trace_id and trace_id not in updated_trace_ids: + self.client._create_trace_tags_via_ingestion( + trace_id=trace_id, + tags=normalized_additional_trace_tags, + ) + updated_trace_ids.add(trace_id) + # Update last processed tracking last_item_timestamp = self._get_item_timestamp(item, scope) last_item_id = item_id @@ -1168,6 +1196,7 @@ async def _process_batch_evaluation_item( evaluators: List[EvaluatorFunction], composite_evaluator: Optional[CompositeEvaluatorFunction], metadata: Optional[Dict[str, Any]], + _add_observation_scores_to_trace: bool, evaluator_stats_dict: Dict[str, EvaluatorStats], ) -> Tuple[int, int, int, List[Evaluation]]: """Process a single item: map, evaluate, create scores. @@ -1179,6 +1208,8 @@ async def _process_batch_evaluation_item( evaluators: List of evaluator functions. composite_evaluator: Optional composite evaluator function. metadata: Additional metadata to add to scores. + _add_observation_scores_to_trace: Whether to duplicate + observation-level scores at trace level. evaluator_stats_dict: Dictionary tracking evaluator statistics. Returns: @@ -1226,7 +1257,7 @@ async def _process_batch_evaluation_item( # Create scores for item-level evaluations item_id = self._get_item_id(item, scope) for evaluation in evaluations: - self._create_score_for_scope( + scores_created += self._create_score_for_scope( scope=scope, item_id=item_id, trace_id=cast(ObservationsView, item).trace_id @@ -1234,8 +1265,8 @@ async def _process_batch_evaluation_item( else None, evaluation=evaluation, additional_metadata=metadata, + add_observation_score_to_trace=_add_observation_scores_to_trace, ) - scores_created += 1 # Run composite evaluator if provided and we have evaluations if composite_evaluator and evaluations: @@ -1251,7 +1282,7 @@ async def _process_batch_evaluation_item( # Create scores for all composite evaluations for composite_eval in composite_evals: - self._create_score_for_scope( + composite_scores_created += self._create_score_for_scope( scope=scope, item_id=item_id, trace_id=cast(ObservationsView, item).trace_id @@ -1259,8 +1290,8 @@ async def _process_batch_evaluation_item( else None, evaluation=composite_eval, additional_metadata=metadata, + add_observation_score_to_trace=_add_observation_scores_to_trace, ) - composite_scores_created += 1 # Add composite evaluations to the list evaluations.extend(composite_evals) @@ -1382,7 +1413,8 @@ def _create_score_for_scope( trace_id: Optional[str] = None, evaluation: Evaluation, additional_metadata: Optional[Dict[str, Any]], - ) -> None: + add_observation_score_to_trace: bool = False, + ) -> int: """Create a score linked to the appropriate entity based on scope. Args: @@ -1391,6 +1423,11 @@ def _create_score_for_scope( trace_id: The trace ID of the entity; required if scope=observations evaluation: The evaluation result to create a score from. additional_metadata: Additional metadata to merge with evaluation metadata. + add_observation_score_to_trace: Whether to duplicate observation + score on parent trace as well. + + Returns: + Number of score events created. """ # Merge metadata score_metadata = { @@ -1408,6 +1445,7 @@ def _create_score_for_scope( data_type=evaluation.data_type, # type: ignore[arg-type] config_id=evaluation.config_id, ) + return 1 elif scope == "observations": self.client.create_score( observation_id=item_id, @@ -1419,6 +1457,23 @@ def _create_score_for_scope( data_type=evaluation.data_type, # type: ignore[arg-type] config_id=evaluation.config_id, ) + score_count = 1 + + if add_observation_score_to_trace and trace_id: + self.client.create_score( + trace_id=trace_id, + name=evaluation.name, + value=evaluation.value, # type: ignore + comment=evaluation.comment, + metadata=score_metadata, + data_type=evaluation.data_type, # type: ignore[arg-type] + config_id=evaluation.config_id, + ) + score_count += 1 + + return score_count + + return 0 def _build_timestamp_filter( self, @@ -1519,6 +1574,21 @@ def _get_timestamp_field_for_scope(scope: str) -> str: return "start_time" return "timestamp" # Default + @staticmethod + def _dedupe_tags(tags: Optional[List[str]]) -> List[str]: + """Deduplicate tags while preserving order.""" + if tags is None: + return [] + + deduped: List[str] = [] + seen = set() + for tag in tags: + if tag not in seen: + deduped.append(tag) + seen.add(tag) + + return deduped + def _build_result( self, total_items_fetched: int, From afb636c54b56f7be0d6b24a4cafee5c129eea5e8 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 23 Feb 2026 10:42:33 +0000 Subject: [PATCH 183/296] chore: release v3.14.5 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index b62dba2cc..d1e9207dd 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.4" +__version__ = "3.14.5" diff --git a/pyproject.toml b/pyproject.toml index 271c152aa..cce85e437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.4" +version = "3.14.5" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 42a0f6211ef8cb8eaa3ab629ce86a2c6f4a8add3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:54:15 +0100 Subject: [PATCH 184/296] chore(deps-dev): bump werkzeug from 3.1.5 to 3.1.6 (#1531) --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 76b5321ef..8fd335675 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2524,14 +2524,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "werkzeug" -version = "3.1.5" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"}, - {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] From 89730bf1df370320efe77e2277dcf180182821f9 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 24 Feb 2026 14:30:46 +0200 Subject: [PATCH 185/296] chore: upgrade ruff + merge configurations (#1535) --- .github/workflows/ci.yml | 4 +--- .pre-commit-config.yaml | 7 +++--- CLAUDE.md | 2 -- ci.ruff.toml | 6 ----- poetry.lock | 47 ++++++++++++++++++------------------- pyproject.toml | 19 ++++++++++++++- ruff.toml | 17 -------------- tests/test_datasets.py | 4 ++-- tests/test_error_logging.py | 3 ++- 9 files changed, 49 insertions(+), 60 deletions(-) delete mode 100644 ci.ruff.toml delete mode 100644 ruff.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 344225dcb..74013b0b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: chartboost/ruff-action@v1 - with: - args: check --config ci.ruff.toml + - uses: astral-sh/ruff-action@v3 type-checking: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b62426d7b..efd690431 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,15 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.4 + rev: v0.15.2 hooks: # Run the linter and fix - - id: ruff + - id: ruff-check types_or: [python, pyi, jupyter] - args: [--fix, --config=ci.ruff.toml] + args: [--fix] # Run the formatter. - id: ruff-format types_or: [python, pyi, jupyter] - args: [--config=ci.ruff.toml] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.18.2 diff --git a/CLAUDE.md b/CLAUDE.md index 01afe275d..6dc8afbb2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,8 +114,6 @@ Environment variables (defined in `_client/environment_variables.py`): ## Important Files - `pyproject.toml`: Poetry configuration, dependencies, and tool settings -- `ruff.toml`: Local development linting config (stricter) -- `ci.ruff.toml`: CI linting config (more permissive) - `langfuse/version.py`: Version string (updated by CI release workflow) ## API Generation diff --git a/ci.ruff.toml b/ci.ruff.toml deleted file mode 100644 index 184e12e6f..000000000 --- a/ci.ruff.toml +++ /dev/null @@ -1,6 +0,0 @@ -# This is the Ruff config used in CI. -# In development, ruff.toml is used instead. - -target-version = 'py38' -[lint] -exclude = ["langfuse/api/**/*.py"] \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 8fd335675..259a556b1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -650,7 +650,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -2219,31 +2219,30 @@ files = [ [[package]] name = "ruff" -version = "0.12.12" +version = "0.15.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc"}, - {file = "ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727"}, - {file = "ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee"}, - {file = "ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1"}, - {file = "ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d"}, - {file = "ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093"}, - {file = "ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6"}, + {file = "ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d"}, + {file = "ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e"}, + {file = "ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8"}, + {file = "ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f"}, + {file = "ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5"}, + {file = "ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e"}, + {file = "ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342"}, ] [[package]] @@ -2911,9 +2910,9 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "bb4ec20d58e29f5d71599357de616571eeae016818d5c246774f0c5bc01d3d0e" +content-hash = "54f130103b57e5bbd62fd69c8c16706233e8ddcea0c94f3166e3f01e787bdf32" diff --git a/pyproject.toml b/pyproject.toml index cce85e437..02f913866 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ pytest-xdist = "^3.3.1" pre-commit = "^3.2.2" pytest-asyncio = ">=0.21.1,<1.2.0" pytest-httpserver = "^1.0.8" -ruff = ">=0.1.8,<0.13.0" +ruff = "^0.15.2" mypy = "^1.0.0" langchain-openai = ">=0.0.5,<0.4" langchain = ">=1" @@ -101,3 +101,20 @@ module = [ ] ignore_errors = true +[tool.ruff] +target-version = "py310" + +[tool.ruff.lint] +extend-select = [ + "I", # Import formatting + # These used to be enabled in the "local ruff.toml", + # which was never enforced; hence, enabling these will + # cause a large number of linting errors. + + # "D", # Docstring lints + # "D401", # Enforce imperative mood in docstrings +] +exclude = ["langfuse/api/**/*.py"] + +[tool.ruff.lint.pydocstyle] +convention = "google" diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index 42843016d..000000000 --- a/ruff.toml +++ /dev/null @@ -1,17 +0,0 @@ -# This is the Ruff config used locally. -# In CI, ci.ruff.toml is used instead. - -target-version = 'py38' - -[lint] -select = [ - # Enforce the use of Google-style docstrings. - "D", - # Augment the convention by requiring an imperative mood for all docstrings. - "D401", -] -exclude = ["langfuse/api/**/*.py"] - -[lint.pydocstyle] -convention = "google" - diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 1a0cda63a..f20c76f24 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -1,7 +1,7 @@ import json import time -from datetime import timedelta from concurrent.futures import ThreadPoolExecutor +from datetime import timedelta from typing import Sequence from langchain_core.prompts import PromptTemplate @@ -573,8 +573,8 @@ def test_get_dataset_with_version(): def test_run_experiment_with_versioned_dataset(): """Test that running an experiment on a versioned dataset works correctly.""" - from datetime import timedelta import time + from datetime import timedelta langfuse = Langfuse(debug=False) diff --git a/tests/test_error_logging.py b/tests/test_error_logging.py index 637a8de98..fd5224d7a 100644 --- a/tests/test_error_logging.py +++ b/tests/test_error_logging.py @@ -1,9 +1,10 @@ import logging + import pytest from langfuse._utils.error_logging import ( - catch_and_log_errors, auto_decorate_methods_with, + catch_and_log_errors, ) From cfd3ac17d79bb7db6c821cfaf0eb3f908406b93f Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:52:20 +0100 Subject: [PATCH 186/296] fix(media): reuse httpx client for requests (#1537) --- langfuse/_client/resource_manager.py | 1 + langfuse/_task_manager/media_manager.py | 10 +- langfuse/media.py | 21 +++- poetry.lock | 123 +++--------------------- pyproject.toml | 1 - tests/test_additional_headers_simple.py | 15 +++ tests/test_media.py | 36 +++++++ 7 files changed, 88 insertions(+), 119 deletions(-) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index e1b3aa7a9..7422bc76e 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -249,6 +249,7 @@ def _initialize_instance( self._media_upload_queue: Queue[Any] = Queue(100_000) self._media_manager = MediaManager( api_client=self.api, + httpx_client=self.httpx_client, media_upload_queue=self._media_upload_queue, max_retries=3, ) diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index 1a32e3d60..de8dfb576 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -5,7 +5,7 @@ from typing import Any, Callable, Optional, TypeVar, cast import backoff -import requests +import httpx from typing_extensions import ParamSpec from langfuse._client.environment_variables import LANGFUSE_MEDIA_UPLOAD_ENABLED @@ -29,10 +29,12 @@ def __init__( self, *, api_client: FernLangfuse, + httpx_client: httpx.Client, media_upload_queue: Queue, max_retries: Optional[int] = 3, ): self._api_client = api_client + self._httpx_client = httpx_client self._queue = media_upload_queue self._max_retries = max_retries self._enabled = os.environ.get( @@ -256,10 +258,10 @@ def _process_upload_media_job( upload_start_time = time.time() upload_response = self._request_with_backoff( - requests.put, + self._httpx_client.put, upload_url, headers=headers, - data=data["content_bytes"], + content=data["content_bytes"], ) upload_time_ms = int((time.time() - upload_start_time) * 1000) @@ -288,7 +290,7 @@ def _should_give_up(e: Exception) -> bool: and 400 <= e.status_code < 500 and e.status_code != 429 ) - if isinstance(e, requests.exceptions.RequestException): + if isinstance(e, httpx.HTTPStatusError): return ( e.response is not None and e.response.status_code < 500 diff --git a/langfuse/media.py b/langfuse/media.py index 6691785af..c87626785 100644 --- a/langfuse/media.py +++ b/langfuse/media.py @@ -7,7 +7,7 @@ import re from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple, TypeVar, cast -import requests +import httpx if TYPE_CHECKING: from langfuse._client.client import Langfuse @@ -284,6 +284,11 @@ def traverse(obj: Any, depth: int) -> Any: result = obj reference_string_to_media_content = {} + httpx_client = ( + langfuse_client._resources.httpx_client + if langfuse_client._resources is not None + else None + ) for reference_string in reference_string_matches: try: @@ -293,11 +298,17 @@ def traverse(obj: Any, depth: int) -> Any: media_data = langfuse_client.api.media.get( parsed_media_reference["media_id"] ) - media_content = requests.get( - media_data.url, timeout=content_fetch_timeout_seconds + media_content = ( + httpx_client.get( + media_data.url, + timeout=content_fetch_timeout_seconds, + ) + if httpx_client is not None + else httpx.get( + media_data.url, timeout=content_fetch_timeout_seconds + ) ) - if not media_content.ok: - raise Exception("Failed to fetch media content") + media_content.raise_for_status() base64_media_content = base64.b64encode( media_content.content diff --git a/poetry.lock b/poetry.lock index 259a556b1..e83730daf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -18,7 +17,6 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -39,7 +37,6 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -51,7 +48,6 @@ version = "0.0.130" description = "Universal library for evaluating AI models" optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, @@ -75,7 +71,6 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -87,8 +82,6 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" -groups = ["dev"] -markers = "python_version == \"3.10\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -100,7 +93,6 @@ version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, @@ -112,7 +104,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -124,7 +115,6 @@ version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, @@ -247,7 +237,6 @@ version = "0.14.0" description = "Mustache templating language renderer" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, @@ -259,12 +248,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -272,7 +259,6 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -284,7 +270,6 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -296,8 +281,6 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -315,7 +298,6 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -330,7 +312,6 @@ version = "3.20.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, @@ -342,7 +323,6 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -360,7 +340,6 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -372,7 +351,6 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -394,7 +372,6 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -407,7 +384,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -419,7 +396,6 @@ version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, @@ -434,7 +410,6 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -449,7 +424,6 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -459,12 +433,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -473,7 +447,6 @@ version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -485,7 +458,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -503,7 +475,6 @@ version = "0.11.1" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, @@ -615,7 +586,6 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -630,7 +600,6 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -642,7 +611,6 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -650,7 +618,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.3.6" +jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -664,7 +632,6 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -679,7 +646,6 @@ version = "1.0.1" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" -groups = ["dev"] files = [ {file = "langchain-1.0.1-py3-none-any.whl", hash = "sha256:48fd616413fee4843f12cad49e8b74ad6fc159640142ca885d03bd925cd24503"}, {file = "langchain-1.0.1.tar.gz", hash = "sha256:8bf60e096ca9c06626d9161a46651405ef1d12b5863f7679283666f83f760dc5"}, @@ -713,7 +679,6 @@ version = "1.2.11" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" -groups = ["dev"] files = [ {file = "langchain_core-1.2.11-py3-none-any.whl", hash = "sha256:ae11ceb8dda60d0b9d09e763116e592f1683327c17be5b715f350fd29aee65d3"}, {file = "langchain_core-1.2.11.tar.gz", hash = "sha256:f164bb36602dd74a3a50c1334fca75309ad5ed95767acdfdbb9fa95ce28a1e01"}, @@ -735,7 +700,6 @@ version = "0.3.34" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0.0,>=3.9.0" -groups = ["dev"] files = [ {file = "langchain_openai-0.3.34-py3-none-any.whl", hash = "sha256:08d61d68a6d869c70d542171e149b9065668dedfc4fafcd4de8aeb5b933030a9"}, {file = "langchain_openai-0.3.34.tar.gz", hash = "sha256:57916d462be5b8fd19e5cb2f00d4e5cf0465266a292d583de2fc693a55ba190e"}, @@ -752,7 +716,6 @@ version = "1.0.2" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "langgraph-1.0.2-py3-none-any.whl", hash = "sha256:b3d56b8c01de857b5fb1da107e8eab6e30512a377685eeedb4f76456724c9729"}, {file = "langgraph-1.0.2.tar.gz", hash = "sha256:dae1af08d6025cb1fcaed68f502c01af7d634d9044787c853a46c791cfc52f67"}, @@ -772,7 +735,6 @@ version = "3.0.0" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "langgraph_checkpoint-3.0.0-py3-none-any.whl", hash = "sha256:560beb83e629784ab689212a3d60834fb3196b4bbe1d6ac18e5cad5d85d46010"}, {file = "langgraph_checkpoint-3.0.0.tar.gz", hash = "sha256:f738695ad938878d8f4775d907d9629e9fcd345b1950196effb08f088c52369e"}, @@ -788,7 +750,6 @@ version = "1.0.2" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "langgraph_prebuilt-1.0.2-py3-none-any.whl", hash = "sha256:d9499f7c449fb637ee7b87e3f6a3b74095f4202053c74d33894bd839ea4c57c7"}, {file = "langgraph_prebuilt-1.0.2.tar.gz", hash = "sha256:9896dbabf04f086eb59df4294f54ab5bdb21cd78e27e0a10e695dffd1cc6097d"}, @@ -804,7 +765,6 @@ version = "0.2.9" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "langgraph_sdk-0.2.9-py3-none-any.whl", hash = "sha256:fbf302edadbf0fb343596f91c597794e936ef68eebc0d3e1d358b6f9f72a1429"}, {file = "langgraph_sdk-0.2.9.tar.gz", hash = "sha256:b3bd04c6be4fa382996cd2be8fbc1e7cc94857d2bc6b6f4599a7f2a245975303"}, @@ -820,7 +780,6 @@ version = "0.6.3" description = "Client library to connect to the LangSmith Observability and Evaluation Platform." optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "langsmith-0.6.3-py3-none-any.whl", hash = "sha256:44fdf8084165513e6bede9dda715e7b460b1b3f57ac69f2ca3f03afa911233ec"}, {file = "langsmith-0.6.3.tar.gz", hash = "sha256:33246769c0bb24e2c17e0c34bb21931084437613cd37faf83bd0978a297b826f"}, @@ -837,7 +796,7 @@ uuid-utils = ">=0.12.0,<1.0" zstandard = ">=0.23.0" [package.extras] -claude-agent-sdk = ["claude-agent-sdk (>=0.1.0) ; python_version >= \"3.10\""] +claude-agent-sdk = ["claude-agent-sdk (>=0.1.0)"] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] openai-agents = ["openai-agents (>=0.0.3)"] otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] @@ -850,7 +809,6 @@ version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -949,7 +907,6 @@ version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, @@ -1010,7 +967,6 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -1022,7 +978,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1034,7 +989,6 @@ version = "2.5.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "openai-2.5.0-py3-none-any.whl", hash = "sha256:21380e5f52a71666dbadbf322dd518bdf2b9d11ed0bb3f96bea17310302d6280"}, {file = "openai-2.5.0.tar.gz", hash = "sha256:f8fa7611f96886a0f31ac6b97e58bc0ada494b255ee2cfd51c8eb502cfcb4814"}, @@ -1062,7 +1016,6 @@ version = "1.38.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582"}, {file = "opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12"}, @@ -1078,7 +1031,6 @@ version = "1.38.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a"}, {file = "opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c"}, @@ -1093,7 +1045,6 @@ version = "1.38.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b"}, {file = "opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b"}, @@ -1114,7 +1065,6 @@ version = "0.59b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee"}, {file = "opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc"}, @@ -1132,7 +1082,6 @@ version = "0.59b0" description = "Thread context propagation support for OpenTelemetry" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca"}, {file = "opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3"}, @@ -1149,7 +1098,6 @@ version = "1.38.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18"}, {file = "opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468"}, @@ -1164,7 +1112,6 @@ version = "1.38.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b"}, {file = "opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe"}, @@ -1181,7 +1128,6 @@ version = "0.59b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed"}, {file = "opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0"}, @@ -1197,7 +1143,6 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1290,7 +1235,6 @@ version = "1.11.0" description = "" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "ormsgpack-1.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:03d4e658dd6e1882a552ce1d13cc7b49157414e7d56a4091fbe7823225b08cba"}, {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb67eb913c2b703f0ed39607fc56e50724dd41f92ce080a586b4d6149eb3fe4"}, @@ -1356,7 +1300,6 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1368,7 +1311,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1380,7 +1322,6 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1397,7 +1338,6 @@ version = "4.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, @@ -1414,7 +1354,6 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1430,7 +1369,6 @@ version = "0.9.0" description = "A fast C-implemented library for Levenshtein distance" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, @@ -1495,7 +1433,6 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1514,7 +1451,6 @@ version = "6.33.5" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"}, {file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"}, @@ -1534,7 +1470,6 @@ version = "2.12.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, @@ -1548,7 +1483,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -1556,7 +1491,6 @@ version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, @@ -1686,7 +1620,6 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1701,7 +1634,6 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1725,7 +1657,6 @@ version = "1.1.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_asyncio-1.1.1-py3-none-any.whl", hash = "sha256:726339d30fcfde24691f589445b9b67d058b311ac632b1d704e97f20f1d878da"}, {file = "pytest_asyncio-1.1.1.tar.gz", hash = "sha256:b72d215c38e2c91dbb32f275e0b5be69602d7869910e109360e375129960a649"}, @@ -1745,7 +1676,6 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1760,7 +1690,6 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1775,7 +1704,6 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1796,7 +1724,6 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -1879,7 +1806,6 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" -groups = ["dev"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -1896,7 +1822,6 @@ version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, @@ -2021,7 +1946,6 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -2043,7 +1967,6 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -2058,7 +1981,6 @@ version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, @@ -2223,7 +2145,6 @@ version = "0.15.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d"}, {file = "ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e"}, @@ -2251,7 +2172,6 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2263,7 +2183,6 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -2279,7 +2198,6 @@ version = "0.12.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, @@ -2353,8 +2271,6 @@ version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -2406,7 +2322,6 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2428,7 +2343,6 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2440,7 +2354,6 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -2455,17 +2368,16 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] +zstd = ["backports-zstd (>=1.0.0)"] [[package]] name = "uuid-utils" @@ -2473,7 +2385,6 @@ version = "0.12.0" description = "Drop-in replacement for Python UUID with bindings in Rust" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514"}, {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65"}, @@ -2505,7 +2416,6 @@ version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, @@ -2519,7 +2429,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "werkzeug" @@ -2527,7 +2437,6 @@ version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, @@ -2545,7 +2454,6 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2636,7 +2544,6 @@ version = "3.6.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, @@ -2786,14 +2693,13 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2806,7 +2712,6 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -2910,9 +2815,9 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] +cffi = ["cffi (>=1.17,<2.0)", "cffi (>=2.0.0b)"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "54f130103b57e5bbd62fd69c8c16706233e8ddcea0c94f3166e3f01e787bdf32" +content-hash = "a9017df10e02338646323994ef23ff03186333a35da4f3cc4196e244802c2683" diff --git a/pyproject.toml b/pyproject.toml index 02f913866..85cbd8c44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ backoff = ">=1.10.0" openai = { version = ">=0.27.8", optional = true } wrapt = "^1.14" packaging = ">=23.2,<26.0" -requests = "^2" opentelemetry-api = "^1.33.1" opentelemetry-sdk = "^1.33.1" opentelemetry-exporter-otlp-proto-http = "^1.33.1" diff --git a/tests/test_additional_headers_simple.py b/tests/test_additional_headers_simple.py index 1f4f836bf..47cc765bd 100644 --- a/tests/test_additional_headers_simple.py +++ b/tests/test_additional_headers_simple.py @@ -100,6 +100,21 @@ def test_custom_httpx_client_without_additional_headers_preserves_client(self): == "existing-value" ) + def test_media_manager_uses_custom_httpx_client(self): + """Test that media manager reuses the configured custom httpx client.""" + custom_client = httpx.Client() + + langfuse = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + host="https://mock-host.com", + httpx_client=custom_client, + tracing_enabled=False, + ) + + assert langfuse._resources is not None + assert langfuse._resources._media_manager._httpx_client is custom_client + def test_none_additional_headers_works(self): """Test that passing None for additional_headers works without errors.""" langfuse = Langfuse( diff --git a/tests/test_media.py b/tests/test_media.py index 088e88334..bea232bf5 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -1,5 +1,7 @@ import base64 import re +from types import SimpleNamespace +from unittest.mock import Mock from uuid import uuid4 import pytest @@ -109,6 +111,40 @@ def test_nonexistent_file(): assert media._content_type is None +def test_resolve_media_references_uses_configured_httpx_client(): + reference_string = "@@@langfuseMedia:type=image/jpeg|id=test-id|source=bytes@@@" + fetch_timeout_seconds = 7 + + media_api = Mock() + media_api.get.return_value = SimpleNamespace( + url="https://example.com/test.jpg", content_type="image/jpeg" + ) + + response = Mock() + response.content = b"test-bytes" + response.raise_for_status.return_value = None + + httpx_client = Mock() + httpx_client.get.return_value = response + + mock_langfuse_client = SimpleNamespace( + api=SimpleNamespace(media=media_api), + _resources=SimpleNamespace(httpx_client=httpx_client), + ) + + resolved = LangfuseMedia.resolve_media_references( + obj={"image": reference_string}, + langfuse_client=mock_langfuse_client, + resolve_with="base64_data_uri", + content_fetch_timeout_seconds=fetch_timeout_seconds, + ) + + assert resolved["image"] == "data:image/jpeg;base64,dGVzdC1ieXRlcw==" + httpx_client.get.assert_called_once_with( + "https://example.com/test.jpg", timeout=fetch_timeout_seconds + ) + + def test_replace_media_reference_string_in_object(): # Create test audio file audio_file = "static/joke_prompt.wav" From df63577a407f764d08e3abb97045c98595a673bf Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:08:20 +0100 Subject: [PATCH 187/296] feat!: prepare v4 release (#1486) --- .github/workflows/ci.yml | 1 + langfuse/__init__.py | 12 + langfuse/_client/attributes.py | 19 +- langfuse/_client/client.py | 575 +- langfuse/_client/datasets.py | 162 +- langfuse/_client/get_client.py | 1 + langfuse/_client/propagation.py | 12 +- langfuse/_client/resource_manager.py | 15 +- langfuse/_client/span.py | 521 +- langfuse/_client/span_filter.py | 76 + langfuse/_client/span_processor.py | 48 +- langfuse/_task_manager/media_manager.py | 30 +- .../_task_manager/score_ingestion_consumer.py | 18 +- langfuse/_utils/parse_error.py | 6 +- langfuse/_utils/serializer.py | 31 +- langfuse/api/.fern/metadata.json | 14 + langfuse/api/README.md | 161 - langfuse/api/__init__.py | 857 +- langfuse/api/annotation_queues/__init__.py | 82 + langfuse/api/annotation_queues/client.py | 1111 +++ langfuse/api/annotation_queues/raw_client.py | 2288 +++++ .../api/annotation_queues/types/__init__.py | 84 + .../types/annotation_queue.py | 28 + .../annotation_queue_assignment_request.py | 16 + .../types/annotation_queue_item.py | 34 + .../types/annotation_queue_object_type.py | 5 +- .../types/annotation_queue_status.py | 5 +- ...te_annotation_queue_assignment_response.py | 18 + .../create_annotation_queue_item_request.py | 25 + .../types/create_annotation_queue_request.py | 20 + ...te_annotation_queue_assignment_response.py | 14 + .../delete_annotation_queue_item_response.py | 15 + .../types/paginated_annotation_queue_items.py | 17 + .../types/paginated_annotation_queues.py | 17 + .../update_annotation_queue_item_request.py | 15 + .../api/blob_storage_integrations/__init__.py | 67 + .../api/blob_storage_integrations/client.py | 463 + .../blob_storage_integrations/raw_client.py | 773 ++ .../types/__init__.py | 69 + .../types/blob_storage_export_frequency.py | 5 +- .../types/blob_storage_export_mode.py | 5 +- ...b_storage_integration_deletion_response.py | 14 + .../blob_storage_integration_file_type.py | 5 +- .../blob_storage_integration_response.py | 58 + .../types/blob_storage_integration_type.py | 26 + .../blob_storage_integrations_response.py | 15 + ...create_blob_storage_integration_request.py | 91 + langfuse/api/client.py | 728 +- langfuse/api/comments/__init__.py | 44 + langfuse/api/comments/client.py | 407 + langfuse/api/comments/raw_client.py | 750 ++ langfuse/api/comments/types/__init__.py | 46 + .../comments/types/create_comment_request.py | 47 + .../comments/types/create_comment_response.py | 17 + .../comments/types/get_comments_response.py | 17 + langfuse/api/commons/__init__.py | 207 + langfuse/api/commons/errors/__init__.py | 56 + .../api/commons/errors/access_denied_error.py | 12 + langfuse/api/commons/errors/error.py | 12 + .../errors/method_not_allowed_error.py | 12 + .../api/commons/errors/not_found_error.py | 12 + .../api/commons/errors/unauthorized_error.py | 12 + langfuse/api/commons/types/__init__.py | 187 + langfuse/api/commons/types/base_score.py | 90 + langfuse/api/commons/types/base_score_v1.py | 70 + langfuse/api/commons/types/boolean_score.py | 26 + .../api/commons/types/boolean_score_v1.py | 26 + .../api/commons/types/categorical_score.py | 26 + .../api/commons/types/categorical_score_v1.py | 26 + langfuse/api/commons/types/comment.py | 36 + .../commons/types/comment_object_type.py | 5 +- langfuse/api/commons/types/config_category.py | 15 + .../api/commons/types/correction_score.py | 26 + .../commons/types/create_score_value.py | 0 langfuse/api/commons/types/dataset.py | 49 + langfuse/api/commons/types/dataset_item.py | 58 + langfuse/api/commons/types/dataset_run.py | 63 + .../api/commons/types/dataset_run_item.py | 40 + .../commons/types/dataset_run_with_items.py | 19 + .../commons/types/dataset_status.py | 5 +- .../commons/types/map_value.py | 1 + .../{resources => }/commons/types/model.py | 101 +- langfuse/api/commons/types/model_price.py | 14 + .../commons/types/model_usage_unit.py | 5 +- langfuse/api/commons/types/numeric_score.py | 17 + .../api/commons/types/numeric_score_v1.py | 17 + langfuse/api/commons/types/observation.py | 142 + .../commons/types/observation_level.py | 5 +- .../api/commons/types/observations_view.py | 89 + .../commons/types/pricing_tier.py | 58 +- .../commons/types/pricing_tier_condition.py | 56 +- .../commons/types/pricing_tier_input.py | 56 +- .../commons/types/pricing_tier_operator.py | 5 +- langfuse/api/commons/types/score.py | 201 + langfuse/api/commons/types/score_config.py | 66 + .../commons/types/score_config_data_type.py | 5 +- .../commons/types/score_data_type.py | 5 +- .../commons/types/score_source.py | 5 +- langfuse/api/commons/types/score_v1.py | 131 + langfuse/api/commons/types/session.py | 25 + .../api/commons/types/session_with_traces.py | 15 + langfuse/api/commons/types/trace.py | 84 + .../api/commons/types/trace_with_details.py | 43 + .../commons/types/trace_with_full_details.py | 45 + langfuse/api/commons/types/usage.py | 59 + langfuse/api/core/__init__.py | 105 +- langfuse/api/core/api_error.py | 18 +- langfuse/api/core/client_wrapper.py | 37 +- langfuse/api/core/enum.py | 20 + langfuse/api/core/file.py | 49 +- langfuse/api/core/force_multipart.py | 18 + langfuse/api/core/http_client.py | 425 +- langfuse/api/core/http_response.py | 55 + langfuse/api/core/http_sse/__init__.py | 48 + langfuse/api/core/http_sse/_api.py | 114 + langfuse/api/core/http_sse/_decoders.py | 66 + .../http_sse/_exceptions.py} | 6 +- langfuse/api/core/http_sse/_models.py | 17 + langfuse/api/core/jsonable_encoder.py | 64 +- langfuse/api/core/pydantic_utilities.py | 296 +- langfuse/api/core/query_encoder.py | 55 +- langfuse/api/core/request_options.py | 3 + langfuse/api/core/serialization.py | 282 + langfuse/api/dataset_items/__init__.py | 52 + langfuse/api/dataset_items/client.py | 499 + langfuse/api/dataset_items/raw_client.py | 973 ++ langfuse/api/dataset_items/types/__init__.py | 50 + .../types/create_dataset_item_request.py | 37 + .../types/delete_dataset_item_response.py | 17 + .../types/paginated_dataset_items.py | 17 + langfuse/api/dataset_run_items/__init__.py | 43 + langfuse/api/dataset_run_items/client.py | 323 + langfuse/api/dataset_run_items/raw_client.py | 547 ++ .../api/dataset_run_items/types/__init__.py | 44 + .../types/create_dataset_run_item_request.py | 51 + .../types/paginated_dataset_run_items.py | 17 + langfuse/api/datasets/__init__.py | 55 + langfuse/api/datasets/client.py | 661 ++ langfuse/api/datasets/raw_client.py | 1368 +++ langfuse/api/datasets/types/__init__.py | 53 + .../datasets/types/create_dataset_request.py | 31 + .../types/delete_dataset_run_response.py | 14 + .../datasets/types/paginated_dataset_runs.py | 17 + .../api/datasets/types/paginated_datasets.py | 17 + langfuse/api/health/__init__.py | 44 + langfuse/api/health/client.py | 112 + langfuse/api/health/errors/__init__.py | 42 + .../errors/service_unavailable_error.py | 13 + langfuse/api/health/raw_client.py | 227 + langfuse/api/health/types/__init__.py | 40 + langfuse/api/health/types/health_response.py | 30 + langfuse/api/ingestion/__init__.py | 169 + .../api/{resources => }/ingestion/client.py | 121 +- langfuse/api/ingestion/raw_client.py | 293 + langfuse/api/ingestion/types/__init__.py | 169 + langfuse/api/ingestion/types/base_event.py | 27 + .../api/ingestion/types/create_event_body.py | 14 + .../api/ingestion/types/create_event_event.py | 15 + .../ingestion/types/create_generation_body.py | 40 + .../types/create_generation_event.py | 15 + .../types/create_observation_event.py | 15 + .../api/ingestion/types/create_span_body.py | 19 + .../api/ingestion/types/create_span_event.py | 15 + .../api/ingestion/types/ingestion_error.py | 17 + .../api/ingestion/types/ingestion_event.py | 155 + .../api/ingestion/types/ingestion_response.py | 17 + .../api/ingestion/types/ingestion_success.py | 15 + .../ingestion/types/ingestion_usage.py | 0 .../api/ingestion/types/observation_body.py | 53 + .../ingestion/types/observation_type.py | 5 +- .../types/open_ai_completion_usage_schema.py | 26 + .../types/open_ai_response_usage_schema.py | 24 + langfuse/api/ingestion/types/open_ai_usage.py | 28 + .../types/optional_observation_body.py | 36 + langfuse/api/ingestion/types/score_body.py | 75 + langfuse/api/ingestion/types/score_event.py | 15 + langfuse/api/ingestion/types/sdk_log_body.py | 14 + langfuse/api/ingestion/types/sdk_log_event.py | 15 + langfuse/api/ingestion/types/trace_body.py | 36 + langfuse/api/ingestion/types/trace_event.py | 15 + .../api/ingestion/types/update_event_body.py | 14 + .../ingestion/types/update_generation_body.py | 40 + .../types/update_generation_event.py | 15 + .../types/update_observation_event.py | 15 + .../api/ingestion/types/update_span_body.py | 19 + .../api/ingestion/types/update_span_event.py | 15 + .../ingestion/types/usage_details.py | 0 langfuse/api/llm_connections/__init__.py | 55 + langfuse/api/llm_connections/client.py | 311 + langfuse/api/llm_connections/raw_client.py | 541 ++ .../api/llm_connections/types/__init__.py | 53 + .../llm_connections/types/llm_adapter.py | 5 +- .../llm_connections/types/llm_connection.py | 77 + .../types/paginated_llm_connections.py | 17 + .../types/upsert_llm_connection_request.py | 69 + langfuse/api/media/__init__.py | 58 + langfuse/api/media/client.py | 427 + langfuse/api/media/raw_client.py | 739 ++ langfuse/api/media/types/__init__.py | 56 + .../api/media/types/get_media_response.py | 55 + .../types/get_media_upload_url_request.py | 51 + .../types/get_media_upload_url_response.py | 28 + .../media/types/media_content_type.py | 37 +- langfuse/api/media/types/patch_media_body.py | 43 + langfuse/api/metrics/__init__.py | 40 + .../api/{resources => }/metrics/client.py | 115 +- langfuse/api/metrics/raw_client.py | 322 + langfuse/api/metrics/types/__init__.py | 40 + .../api/metrics/types/metrics_response.py | 19 + langfuse/api/metrics_v2/__init__.py | 40 + .../metrics_v_2 => metrics_v2}/client.py | 119 +- langfuse/api/metrics_v2/raw_client.py | 530 ++ langfuse/api/metrics_v2/types/__init__.py | 40 + .../metrics_v2/types/metrics_v2response.py | 19 + langfuse/api/models/__init__.py | 43 + langfuse/api/models/client.py | 523 ++ langfuse/api/models/raw_client.py | 993 ++ langfuse/api/models/types/__init__.py | 44 + .../models/types/create_model_request.py | 96 +- langfuse/api/models/types/paginated_models.py | 17 + langfuse/api/observations/__init__.py | 43 + .../{resources => }/observations/client.py | 247 +- langfuse/api/observations/raw_client.py | 759 ++ langfuse/api/observations/types/__init__.py | 44 + .../api/observations/types/observations.py | 17 + .../observations/types/observations_views.py | 17 + langfuse/api/observations_v2/__init__.py | 43 + langfuse/api/observations_v2/client.py | 520 ++ .../raw_client.py} | 217 +- .../api/observations_v2/types/__init__.py | 44 + .../types/observations_v2meta.py | 21 + .../types/observations_v2response.py | 27 + langfuse/api/opentelemetry/__init__.py | 67 + .../{resources => }/opentelemetry/client.py | 121 +- langfuse/api/opentelemetry/raw_client.py | 291 + langfuse/api/opentelemetry/types/__init__.py | 65 + .../api/opentelemetry/types/otel_attribute.py | 27 + .../types/otel_attribute_value.py | 46 + .../api/opentelemetry/types/otel_resource.py | 24 + .../opentelemetry/types/otel_resource_span.py | 32 + .../api/opentelemetry/types/otel_scope.py | 34 + .../opentelemetry/types/otel_scope_span.py | 28 + langfuse/api/opentelemetry/types/otel_span.py | 76 + .../types/otel_trace_response.py | 16 + langfuse/api/organizations/__init__.py | 73 + langfuse/api/organizations/client.py | 756 ++ langfuse/api/organizations/raw_client.py | 1707 ++++ langfuse/api/organizations/types/__init__.py | 71 + .../types/delete_membership_request.py | 16 + .../types/membership_deletion_response.py | 17 + .../organizations/types/membership_request.py | 18 + .../types/membership_response.py | 20 + .../organizations/types/membership_role.py | 5 +- .../types/memberships_response.py | 15 + .../types/organization_api_key.py | 31 + .../types/organization_api_keys_response.py | 19 + .../types/organization_project.py | 25 + .../types/organization_projects_response.py | 15 + langfuse/api/projects/__init__.py | 67 + langfuse/api/projects/client.py | 760 ++ langfuse/api/projects/raw_client.py | 1577 ++++ langfuse/api/projects/types/__init__.py | 65 + .../types/api_key_deletion_response.py | 18 + langfuse/api/projects/types/api_key_list.py | 23 + .../api/projects/types/api_key_response.py | 30 + .../api/projects/types/api_key_summary.py | 35 + langfuse/api/projects/types/organization.py | 22 + langfuse/api/projects/types/project.py | 34 + .../types/project_deletion_response.py | 15 + langfuse/api/projects/types/projects.py | 15 + .../prompt_version/__init__.py | 2 + langfuse/api/prompt_version/client.py | 157 + langfuse/api/prompt_version/raw_client.py | 264 + langfuse/api/prompts/__init__.py | 100 + langfuse/api/prompts/client.py | 534 ++ langfuse/api/prompts/raw_client.py | 977 ++ langfuse/api/prompts/types/__init__.py | 96 + langfuse/api/prompts/types/base_prompt.py | 42 + langfuse/api/prompts/types/chat_message.py | 17 + .../api/prompts/types/chat_message_type.py | 15 + .../types/chat_message_with_placeholders.py | 8 + langfuse/api/prompts/types/chat_prompt.py | 15 + .../types/create_chat_prompt_request.py | 37 + .../prompts/types/create_chat_prompt_type.py | 15 + .../prompts/types/create_prompt_request.py | 8 + .../types/create_text_prompt_request.py | 36 + .../prompts/types/create_text_prompt_type.py | 15 + .../api/prompts/types/placeholder_message.py | 16 + .../prompts/types/placeholder_message_type.py | 15 + langfuse/api/prompts/types/prompt.py | 58 + langfuse/api/prompts/types/prompt_meta.py | 35 + .../types/prompt_meta_list_response.py | 17 + .../prompts/types/prompt_type.py | 5 +- langfuse/api/prompts/types/text_prompt.py | 14 + langfuse/api/reference.md | 8141 ----------------- langfuse/api/resources/__init__.py | 543 -- .../resources/annotation_queues/__init__.py | 33 - .../api/resources/annotation_queues/client.py | 1642 ---- .../annotation_queues/types/__init__.py | 35 - .../types/annotation_queue.py | 49 - .../annotation_queue_assignment_request.py | 44 - .../types/annotation_queue_item.py | 55 - ...te_annotation_queue_assignment_response.py | 46 - .../create_annotation_queue_item_request.py | 51 - .../types/create_annotation_queue_request.py | 46 - ...te_annotation_queue_assignment_response.py | 42 - .../delete_annotation_queue_item_response.py | 43 - .../types/paginated_annotation_queue_items.py | 45 - .../types/paginated_annotation_queues.py | 45 - .../update_annotation_queue_item_request.py | 43 - .../blob_storage_integrations/__init__.py | 23 - .../blob_storage_integrations/client.py | 492 - .../types/__init__.py | 23 - ...b_storage_integration_deletion_response.py | 42 - .../blob_storage_integration_response.py | 75 - .../types/blob_storage_integration_type.py | 25 - .../blob_storage_integrations_response.py | 43 - ...create_blob_storage_integration_request.py | 108 - langfuse/api/resources/comments/__init__.py | 5 - langfuse/api/resources/comments/client.py | 520 -- .../api/resources/comments/types/__init__.py | 7 - .../comments/types/create_comment_request.py | 69 - .../comments/types/create_comment_response.py | 45 - .../comments/types/get_comments_response.py | 45 - langfuse/api/resources/commons/__init__.py | 117 - .../api/resources/commons/errors/__init__.py | 15 - .../commons/errors/access_denied_error.py | 10 - .../api/resources/commons/errors/error.py | 10 - .../errors/method_not_allowed_error.py | 10 - .../commons/errors/not_found_error.py | 10 - .../commons/errors/unauthorized_error.py | 10 - .../api/resources/commons/types/__init__.py | 102 - .../api/resources/commons/types/base_score.py | 107 - .../resources/commons/types/base_score_v_1.py | 89 - .../resources/commons/types/boolean_score.py | 53 - .../commons/types/boolean_score_v_1.py | 53 - .../commons/types/categorical_score.py | 53 - .../commons/types/categorical_score_v_1.py | 53 - .../api/resources/commons/types/comment.py | 57 - .../commons/types/config_category.py | 43 - .../commons/types/correction_score.py | 53 - .../api/resources/commons/types/dataset.py | 72 - .../resources/commons/types/dataset_item.py | 79 - .../resources/commons/types/dataset_run.py | 82 - .../commons/types/dataset_run_item.py | 57 - .../commons/types/dataset_run_with_items.py | 48 - .../resources/commons/types/model_price.py | 42 - .../resources/commons/types/numeric_score.py | 48 - .../commons/types/numeric_score_v_1.py | 48 - .../resources/commons/types/observation.py | 157 - .../commons/types/observations_view.py | 116 - langfuse/api/resources/commons/types/score.py | 272 - .../resources/commons/types/score_config.py | 85 - .../api/resources/commons/types/score_v_1.py | 189 - .../api/resources/commons/types/session.py | 50 - .../commons/types/session_with_traces.py | 46 - langfuse/api/resources/commons/types/trace.py | 109 - .../commons/types/trace_with_details.py | 70 - .../commons/types/trace_with_full_details.py | 72 - langfuse/api/resources/commons/types/usage.py | 87 - .../api/resources/dataset_items/__init__.py | 13 - .../api/resources/dataset_items/client.py | 658 -- .../resources/dataset_items/types/__init__.py | 11 - .../types/create_dataset_item_request.py | 65 - .../types/delete_dataset_item_response.py | 45 - .../types/paginated_dataset_items.py | 45 - .../resources/dataset_run_items/__init__.py | 5 - .../api/resources/dataset_run_items/client.py | 366 - .../dataset_run_items/types/__init__.py | 6 - .../types/create_dataset_run_item_request.py | 74 - .../types/paginated_dataset_run_items.py | 45 - langfuse/api/resources/datasets/__init__.py | 15 - langfuse/api/resources/datasets/client.py | 942 -- .../api/resources/datasets/types/__init__.py | 13 - .../datasets/types/create_dataset_request.py | 59 - .../types/delete_dataset_run_response.py | 42 - .../datasets/types/paginated_dataset_runs.py | 45 - .../datasets/types/paginated_datasets.py | 45 - langfuse/api/resources/health/__init__.py | 6 - langfuse/api/resources/health/client.py | 154 - .../api/resources/health/errors/__init__.py | 5 - .../errors/service_unavailable_error.py | 8 - .../api/resources/health/types/__init__.py | 5 - .../resources/health/types/health_response.py | 58 - langfuse/api/resources/ingestion/__init__.py | 91 - .../api/resources/ingestion/types/__init__.py | 91 - .../resources/ingestion/types/base_event.py | 55 - .../ingestion/types/create_event_body.py | 45 - .../ingestion/types/create_event_event.py | 46 - .../ingestion/types/create_generation_body.py | 67 - .../types/create_generation_event.py | 46 - .../types/create_observation_event.py | 46 - .../ingestion/types/create_span_body.py | 47 - .../ingestion/types/create_span_event.py | 46 - .../ingestion/types/ingestion_error.py | 45 - .../ingestion/types/ingestion_event.py | 422 - .../ingestion/types/ingestion_response.py | 45 - .../ingestion/types/ingestion_success.py | 43 - .../ingestion/types/observation_body.py | 77 - .../types/open_ai_completion_usage_schema.py | 54 - .../types/open_ai_response_usage_schema.py | 52 - .../ingestion/types/open_ai_usage.py | 56 - .../types/optional_observation_body.py | 61 - .../resources/ingestion/types/score_body.py | 97 - .../resources/ingestion/types/score_event.py | 46 - .../resources/ingestion/types/sdk_log_body.py | 42 - .../ingestion/types/sdk_log_event.py | 46 - .../resources/ingestion/types/trace_body.py | 61 - .../resources/ingestion/types/trace_event.py | 46 - .../ingestion/types/update_event_body.py | 45 - .../ingestion/types/update_generation_body.py | 67 - .../types/update_generation_event.py | 46 - .../types/update_observation_event.py | 46 - .../ingestion/types/update_span_body.py | 47 - .../ingestion/types/update_span_event.py | 46 - .../api/resources/llm_connections/__init__.py | 15 - .../api/resources/llm_connections/client.py | 340 - .../llm_connections/types/__init__.py | 13 - .../llm_connections/types/llm_connection.py | 92 - .../types/paginated_llm_connections.py | 45 - .../types/upsert_llm_connection_request.py | 95 - langfuse/api/resources/media/__init__.py | 17 - langfuse/api/resources/media/client.py | 505 - .../api/resources/media/types/__init__.py | 15 - .../media/types/get_media_response.py | 72 - .../types/get_media_upload_url_request.py | 71 - .../types/get_media_upload_url_response.py | 54 - .../resources/media/types/patch_media_body.py | 66 - langfuse/api/resources/metrics/__init__.py | 5 - .../api/resources/metrics/types/__init__.py | 5 - .../metrics/types/metrics_response.py | 47 - .../api/resources/metrics_v_2/__init__.py | 5 - .../resources/metrics_v_2/types/__init__.py | 5 - .../metrics_v_2/types/metrics_v_2_response.py | 47 - langfuse/api/resources/models/__init__.py | 5 - langfuse/api/resources/models/client.py | 607 -- .../api/resources/models/types/__init__.py | 6 - .../models/types/paginated_models.py | 45 - .../api/resources/observations/__init__.py | 5 - .../resources/observations/types/__init__.py | 6 - .../observations/types/observations.py | 45 - .../observations/types/observations_views.py | 45 - .../resources/observations_v_2/__init__.py | 5 - .../observations_v_2/types/__init__.py | 6 - .../types/observations_v_2_meta.py | 49 - .../types/observations_v_2_response.py | 55 - .../api/resources/opentelemetry/__init__.py | 23 - .../resources/opentelemetry/types/__init__.py | 21 - .../opentelemetry/types/otel_attribute.py | 55 - .../types/otel_attribute_value.py | 72 - .../opentelemetry/types/otel_resource.py | 52 - .../opentelemetry/types/otel_resource_span.py | 60 - .../opentelemetry/types/otel_scope.py | 62 - .../opentelemetry/types/otel_scope_span.py | 56 - .../opentelemetry/types/otel_span.py | 104 - .../types/otel_trace_response.py | 44 - .../api/resources/organizations/__init__.py | 27 - .../api/resources/organizations/client.py | 1205 --- .../resources/organizations/types/__init__.py | 25 - .../types/delete_membership_request.py | 44 - .../types/membership_deletion_response.py | 45 - .../organizations/types/membership_request.py | 46 - .../types/membership_response.py | 48 - .../types/memberships_response.py | 43 - .../types/organization_api_key.py | 54 - .../types/organization_api_keys_response.py | 45 - .../types/organization_project.py | 48 - .../types/organization_projects_response.py | 43 - langfuse/api/resources/projects/__init__.py | 23 - langfuse/api/resources/projects/client.py | 1110 --- .../api/resources/projects/types/__init__.py | 21 - .../types/api_key_deletion_response.py | 46 - .../resources/projects/types/api_key_list.py | 49 - .../projects/types/api_key_response.py | 53 - .../projects/types/api_key_summary.py | 58 - .../resources/projects/types/organization.py | 50 - .../api/resources/projects/types/project.py | 62 - .../types/project_deletion_response.py | 43 - .../api/resources/projects/types/projects.py | 43 - .../api/resources/prompt_version/client.py | 199 - langfuse/api/resources/prompts/__init__.py | 45 - langfuse/api/resources/prompts/client.py | 749 -- .../api/resources/prompts/types/__init__.py | 45 - .../resources/prompts/types/base_prompt.py | 69 - .../resources/prompts/types/chat_message.py | 43 - .../types/chat_message_with_placeholders.py | 87 - .../resources/prompts/types/chat_prompt.py | 46 - .../types/create_chat_prompt_request.py | 63 - .../prompts/types/create_prompt_request.py | 103 - .../types/create_text_prompt_request.py | 62 - .../prompts/types/placeholder_message.py | 42 - .../api/resources/prompts/types/prompt.py | 111 - .../resources/prompts/types/prompt_meta.py | 58 - .../types/prompt_meta_list_response.py | 45 - .../resources/prompts/types/text_prompt.py | 45 - langfuse/api/resources/scim/__init__.py | 41 - langfuse/api/resources/scim/client.py | 1042 --- langfuse/api/resources/scim/types/__init__.py | 39 - .../scim/types/authentication_scheme.py | 48 - .../api/resources/scim/types/bulk_config.py | 46 - .../resources/scim/types/empty_response.py | 44 - .../api/resources/scim/types/filter_config.py | 45 - .../api/resources/scim/types/resource_meta.py | 45 - .../api/resources/scim/types/resource_type.py | 55 - .../scim/types/resource_types_response.py | 47 - .../resources/scim/types/schema_extension.py | 45 - .../resources/scim/types/schema_resource.py | 47 - .../resources/scim/types/schemas_response.py | 47 - .../api/resources/scim/types/scim_email.py | 44 - .../scim/types/scim_feature_support.py | 42 - .../api/resources/scim/types/scim_name.py | 42 - .../api/resources/scim/types/scim_user.py | 52 - .../scim/types/scim_users_list_response.py | 49 - .../scim/types/service_provider_config.py | 60 - .../api/resources/scim/types/user_meta.py | 48 - langfuse/api/resources/score/__init__.py | 5 - langfuse/api/resources/score/client.py | 322 - .../api/resources/score/types/__init__.py | 6 - .../score/types/create_score_request.py | 97 - .../score/types/create_score_response.py | 45 - .../api/resources/score_configs/__init__.py | 5 - .../api/resources/score_configs/client.py | 632 -- .../resources/score_configs/types/__init__.py | 7 - .../types/create_score_config_request.py | 72 - .../score_configs/types/score_configs.py | 45 - .../types/update_score_config_request.py | 81 - langfuse/api/resources/score_v_2/__init__.py | 29 - .../api/resources/score_v_2/types/__init__.py | 29 - .../score_v_2/types/get_scores_response.py | 45 - .../types/get_scores_response_data.py | 282 - .../types/get_scores_response_data_boolean.py | 46 - .../get_scores_response_data_categorical.py | 46 - .../get_scores_response_data_correction.py | 46 - .../types/get_scores_response_data_numeric.py | 46 - .../types/get_scores_response_trace_data.py | 57 - langfuse/api/resources/sessions/__init__.py | 5 - langfuse/api/resources/sessions/client.py | 367 - .../api/resources/sessions/types/__init__.py | 5 - .../sessions/types/paginated_sessions.py | 45 - langfuse/api/resources/trace/__init__.py | 5 - .../api/resources/trace/types/__init__.py | 7 - .../trace/types/delete_trace_response.py | 42 - langfuse/api/resources/trace/types/sort.py | 42 - langfuse/api/resources/trace/types/traces.py | 45 - langfuse/api/resources/utils/__init__.py | 5 - .../api/resources/utils/resources/__init__.py | 6 - .../resources/pagination/types/__init__.py | 5 - .../pagination/types/meta_response.py | 62 - langfuse/api/scim/__init__.py | 94 + langfuse/api/scim/client.py | 686 ++ langfuse/api/scim/raw_client.py | 1528 ++++ langfuse/api/scim/types/__init__.py | 92 + .../api/scim/types/authentication_scheme.py | 20 + langfuse/api/scim/types/bulk_config.py | 22 + langfuse/api/scim/types/empty_response.py | 16 + langfuse/api/scim/types/filter_config.py | 17 + langfuse/api/scim/types/resource_meta.py | 17 + langfuse/api/scim/types/resource_type.py | 27 + .../api/scim/types/resource_types_response.py | 21 + langfuse/api/scim/types/schema_extension.py | 17 + langfuse/api/scim/types/schema_resource.py | 19 + langfuse/api/scim/types/schemas_response.py | 21 + langfuse/api/scim/types/scim_email.py | 16 + .../api/scim/types/scim_feature_support.py | 14 + langfuse/api/scim/types/scim_name.py | 14 + langfuse/api/scim/types/scim_user.py | 24 + .../scim/types/scim_users_list_response.py | 25 + .../api/scim/types/service_provider_config.py | 36 + langfuse/api/scim/types/user_meta.py | 20 + langfuse/api/score/__init__.py | 43 + langfuse/api/score/client.py | 329 + langfuse/api/score/raw_client.py | 545 ++ langfuse/api/score/types/__init__.py | 44 + .../api/score/types/create_score_request.py | 75 + .../api/score/types/create_score_response.py | 17 + langfuse/api/score_configs/__init__.py | 44 + langfuse/api/score_configs/client.py | 526 ++ langfuse/api/score_configs/raw_client.py | 1012 ++ langfuse/api/score_configs/types/__init__.py | 46 + .../types/create_score_config_request.py | 46 + .../api/score_configs/types/score_configs.py | 17 + .../types/update_score_config_request.py | 53 + langfuse/api/score_v2/__init__.py | 76 + langfuse/api/score_v2/client.py | 410 + .../client.py => score_v2/raw_client.py} | 413 +- langfuse/api/score_v2/types/__init__.py | 76 + .../api/score_v2/types/get_scores_response.py | 17 + .../types/get_scores_response_data.py | 211 + .../types/get_scores_response_data_boolean.py | 15 + .../get_scores_response_data_categorical.py | 15 + .../get_scores_response_data_correction.py | 15 + .../types/get_scores_response_data_numeric.py | 15 + .../types/get_scores_response_trace_data.py | 31 + langfuse/api/sessions/__init__.py | 40 + langfuse/api/sessions/client.py | 262 + langfuse/api/sessions/raw_client.py | 500 + langfuse/api/sessions/types/__init__.py | 40 + .../api/sessions/types/paginated_sessions.py | 17 + langfuse/api/trace/__init__.py | 44 + langfuse/api/{resources => }/trace/client.py | 399 +- langfuse/api/trace/raw_client.py | 1208 +++ langfuse/api/trace/types/__init__.py | 46 + .../api/trace/types/delete_trace_response.py | 14 + langfuse/api/trace/types/sort.py | 14 + langfuse/api/trace/types/traces.py | 17 + langfuse/api/utils/__init__.py | 44 + langfuse/api/utils/pagination/__init__.py | 40 + .../api/utils/pagination/types/__init__.py | 40 + .../utils/pagination/types/meta_response.py | 38 + langfuse/batch_evaluation.py | 6 +- langfuse/experiment.py | 16 +- langfuse/langchain/CallbackHandler.py | 132 +- langfuse/model.py | 55 +- langfuse/span_filter.py | 17 + langfuse/types.py | 37 +- poetry.lock | 2 +- pyproject.toml | 10 +- tests/test_batch_evaluation.py | 40 +- tests/test_core_sdk.py | 837 +- tests/test_datasets.py | 401 +- tests/test_decorators.py | 150 +- tests/test_deprecation.py | 120 - tests/test_error_parsing.py | 6 +- tests/test_json.py | 2 +- tests/test_langchain.py | 54 +- tests/test_langchain_integration.py | 36 +- tests/test_media.py | 4 +- tests/test_otel.py | 566 +- tests/test_prompt.py | 19 +- tests/test_prompt_compilation.py | 8 +- tests/test_propagate_attributes.py | 502 +- tests/test_resource_manager.py | 16 + tests/test_span_filter.py | 112 + tests/utils.py | 66 +- 634 files changed, 45317 insertions(+), 37176 deletions(-) create mode 100644 langfuse/_client/span_filter.py create mode 100644 langfuse/api/.fern/metadata.json delete mode 100644 langfuse/api/README.md create mode 100644 langfuse/api/annotation_queues/__init__.py create mode 100644 langfuse/api/annotation_queues/client.py create mode 100644 langfuse/api/annotation_queues/raw_client.py create mode 100644 langfuse/api/annotation_queues/types/__init__.py create mode 100644 langfuse/api/annotation_queues/types/annotation_queue.py create mode 100644 langfuse/api/annotation_queues/types/annotation_queue_assignment_request.py create mode 100644 langfuse/api/annotation_queues/types/annotation_queue_item.py rename langfuse/api/{resources => }/annotation_queues/types/annotation_queue_object_type.py (89%) rename langfuse/api/{resources => }/annotation_queues/types/annotation_queue_status.py (87%) create mode 100644 langfuse/api/annotation_queues/types/create_annotation_queue_assignment_response.py create mode 100644 langfuse/api/annotation_queues/types/create_annotation_queue_item_request.py create mode 100644 langfuse/api/annotation_queues/types/create_annotation_queue_request.py create mode 100644 langfuse/api/annotation_queues/types/delete_annotation_queue_assignment_response.py create mode 100644 langfuse/api/annotation_queues/types/delete_annotation_queue_item_response.py create mode 100644 langfuse/api/annotation_queues/types/paginated_annotation_queue_items.py create mode 100644 langfuse/api/annotation_queues/types/paginated_annotation_queues.py create mode 100644 langfuse/api/annotation_queues/types/update_annotation_queue_item_request.py create mode 100644 langfuse/api/blob_storage_integrations/__init__.py create mode 100644 langfuse/api/blob_storage_integrations/client.py create mode 100644 langfuse/api/blob_storage_integrations/raw_client.py create mode 100644 langfuse/api/blob_storage_integrations/types/__init__.py rename langfuse/api/{resources => }/blob_storage_integrations/types/blob_storage_export_frequency.py (89%) rename langfuse/api/{resources => }/blob_storage_integrations/types/blob_storage_export_mode.py (91%) create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py rename langfuse/api/{resources => }/blob_storage_integrations/types/blob_storage_integration_file_type.py (88%) create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_integration_type.py create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_integrations_response.py create mode 100644 langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py create mode 100644 langfuse/api/comments/__init__.py create mode 100644 langfuse/api/comments/client.py create mode 100644 langfuse/api/comments/raw_client.py create mode 100644 langfuse/api/comments/types/__init__.py create mode 100644 langfuse/api/comments/types/create_comment_request.py create mode 100644 langfuse/api/comments/types/create_comment_response.py create mode 100644 langfuse/api/comments/types/get_comments_response.py create mode 100644 langfuse/api/commons/__init__.py create mode 100644 langfuse/api/commons/errors/__init__.py create mode 100644 langfuse/api/commons/errors/access_denied_error.py create mode 100644 langfuse/api/commons/errors/error.py create mode 100644 langfuse/api/commons/errors/method_not_allowed_error.py create mode 100644 langfuse/api/commons/errors/not_found_error.py create mode 100644 langfuse/api/commons/errors/unauthorized_error.py create mode 100644 langfuse/api/commons/types/__init__.py create mode 100644 langfuse/api/commons/types/base_score.py create mode 100644 langfuse/api/commons/types/base_score_v1.py create mode 100644 langfuse/api/commons/types/boolean_score.py create mode 100644 langfuse/api/commons/types/boolean_score_v1.py create mode 100644 langfuse/api/commons/types/categorical_score.py create mode 100644 langfuse/api/commons/types/categorical_score_v1.py create mode 100644 langfuse/api/commons/types/comment.py rename langfuse/api/{resources => }/commons/types/comment_object_type.py (92%) create mode 100644 langfuse/api/commons/types/config_category.py create mode 100644 langfuse/api/commons/types/correction_score.py rename langfuse/api/{resources => }/commons/types/create_score_value.py (100%) create mode 100644 langfuse/api/commons/types/dataset.py create mode 100644 langfuse/api/commons/types/dataset_item.py create mode 100644 langfuse/api/commons/types/dataset_run.py create mode 100644 langfuse/api/commons/types/dataset_run_item.py create mode 100644 langfuse/api/commons/types/dataset_run_with_items.py rename langfuse/api/{resources => }/commons/types/dataset_status.py (88%) rename langfuse/api/{resources => }/commons/types/map_value.py (88%) rename langfuse/api/{resources => }/commons/types/model.py (55%) create mode 100644 langfuse/api/commons/types/model_price.py rename langfuse/api/{resources => }/commons/types/model_usage_unit.py (94%) create mode 100644 langfuse/api/commons/types/numeric_score.py create mode 100644 langfuse/api/commons/types/numeric_score_v1.py create mode 100644 langfuse/api/commons/types/observation.py rename langfuse/api/{resources => }/commons/types/observation_level.py (91%) create mode 100644 langfuse/api/commons/types/observations_view.py rename langfuse/api/{resources => }/commons/types/pricing_tier.py (66%) rename langfuse/api/{resources => }/commons/types/pricing_tier_condition.py (57%) rename langfuse/api/{resources => }/commons/types/pricing_tier_input.py (59%) rename langfuse/api/{resources => }/commons/types/pricing_tier_operator.py (93%) create mode 100644 langfuse/api/commons/types/score.py create mode 100644 langfuse/api/commons/types/score_config.py rename langfuse/api/{resources => }/commons/types/score_config_data_type.py (90%) rename langfuse/api/{resources => }/commons/types/score_data_type.py (92%) rename langfuse/api/{resources => }/commons/types/score_source.py (90%) create mode 100644 langfuse/api/commons/types/score_v1.py create mode 100644 langfuse/api/commons/types/session.py create mode 100644 langfuse/api/commons/types/session_with_traces.py create mode 100644 langfuse/api/commons/types/trace.py create mode 100644 langfuse/api/commons/types/trace_with_details.py create mode 100644 langfuse/api/commons/types/trace_with_full_details.py create mode 100644 langfuse/api/commons/types/usage.py create mode 100644 langfuse/api/core/enum.py create mode 100644 langfuse/api/core/force_multipart.py create mode 100644 langfuse/api/core/http_response.py create mode 100644 langfuse/api/core/http_sse/__init__.py create mode 100644 langfuse/api/core/http_sse/_api.py create mode 100644 langfuse/api/core/http_sse/_decoders.py rename langfuse/api/{resources/utils/resources/pagination/__init__.py => core/http_sse/_exceptions.py} (51%) create mode 100644 langfuse/api/core/http_sse/_models.py create mode 100644 langfuse/api/core/serialization.py create mode 100644 langfuse/api/dataset_items/__init__.py create mode 100644 langfuse/api/dataset_items/client.py create mode 100644 langfuse/api/dataset_items/raw_client.py create mode 100644 langfuse/api/dataset_items/types/__init__.py create mode 100644 langfuse/api/dataset_items/types/create_dataset_item_request.py create mode 100644 langfuse/api/dataset_items/types/delete_dataset_item_response.py create mode 100644 langfuse/api/dataset_items/types/paginated_dataset_items.py create mode 100644 langfuse/api/dataset_run_items/__init__.py create mode 100644 langfuse/api/dataset_run_items/client.py create mode 100644 langfuse/api/dataset_run_items/raw_client.py create mode 100644 langfuse/api/dataset_run_items/types/__init__.py create mode 100644 langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py create mode 100644 langfuse/api/dataset_run_items/types/paginated_dataset_run_items.py create mode 100644 langfuse/api/datasets/__init__.py create mode 100644 langfuse/api/datasets/client.py create mode 100644 langfuse/api/datasets/raw_client.py create mode 100644 langfuse/api/datasets/types/__init__.py create mode 100644 langfuse/api/datasets/types/create_dataset_request.py create mode 100644 langfuse/api/datasets/types/delete_dataset_run_response.py create mode 100644 langfuse/api/datasets/types/paginated_dataset_runs.py create mode 100644 langfuse/api/datasets/types/paginated_datasets.py create mode 100644 langfuse/api/health/__init__.py create mode 100644 langfuse/api/health/client.py create mode 100644 langfuse/api/health/errors/__init__.py create mode 100644 langfuse/api/health/errors/service_unavailable_error.py create mode 100644 langfuse/api/health/raw_client.py create mode 100644 langfuse/api/health/types/__init__.py create mode 100644 langfuse/api/health/types/health_response.py create mode 100644 langfuse/api/ingestion/__init__.py rename langfuse/api/{resources => }/ingestion/client.py (64%) create mode 100644 langfuse/api/ingestion/raw_client.py create mode 100644 langfuse/api/ingestion/types/__init__.py create mode 100644 langfuse/api/ingestion/types/base_event.py create mode 100644 langfuse/api/ingestion/types/create_event_body.py create mode 100644 langfuse/api/ingestion/types/create_event_event.py create mode 100644 langfuse/api/ingestion/types/create_generation_body.py create mode 100644 langfuse/api/ingestion/types/create_generation_event.py create mode 100644 langfuse/api/ingestion/types/create_observation_event.py create mode 100644 langfuse/api/ingestion/types/create_span_body.py create mode 100644 langfuse/api/ingestion/types/create_span_event.py create mode 100644 langfuse/api/ingestion/types/ingestion_error.py create mode 100644 langfuse/api/ingestion/types/ingestion_event.py create mode 100644 langfuse/api/ingestion/types/ingestion_response.py create mode 100644 langfuse/api/ingestion/types/ingestion_success.py rename langfuse/api/{resources => }/ingestion/types/ingestion_usage.py (100%) create mode 100644 langfuse/api/ingestion/types/observation_body.py rename langfuse/api/{resources => }/ingestion/types/observation_type.py (96%) create mode 100644 langfuse/api/ingestion/types/open_ai_completion_usage_schema.py create mode 100644 langfuse/api/ingestion/types/open_ai_response_usage_schema.py create mode 100644 langfuse/api/ingestion/types/open_ai_usage.py create mode 100644 langfuse/api/ingestion/types/optional_observation_body.py create mode 100644 langfuse/api/ingestion/types/score_body.py create mode 100644 langfuse/api/ingestion/types/score_event.py create mode 100644 langfuse/api/ingestion/types/sdk_log_body.py create mode 100644 langfuse/api/ingestion/types/sdk_log_event.py create mode 100644 langfuse/api/ingestion/types/trace_body.py create mode 100644 langfuse/api/ingestion/types/trace_event.py create mode 100644 langfuse/api/ingestion/types/update_event_body.py create mode 100644 langfuse/api/ingestion/types/update_generation_body.py create mode 100644 langfuse/api/ingestion/types/update_generation_event.py create mode 100644 langfuse/api/ingestion/types/update_observation_event.py create mode 100644 langfuse/api/ingestion/types/update_span_body.py create mode 100644 langfuse/api/ingestion/types/update_span_event.py rename langfuse/api/{resources => }/ingestion/types/usage_details.py (100%) create mode 100644 langfuse/api/llm_connections/__init__.py create mode 100644 langfuse/api/llm_connections/client.py create mode 100644 langfuse/api/llm_connections/raw_client.py create mode 100644 langfuse/api/llm_connections/types/__init__.py rename langfuse/api/{resources => }/llm_connections/types/llm_adapter.py (94%) create mode 100644 langfuse/api/llm_connections/types/llm_connection.py create mode 100644 langfuse/api/llm_connections/types/paginated_llm_connections.py create mode 100644 langfuse/api/llm_connections/types/upsert_llm_connection_request.py create mode 100644 langfuse/api/media/__init__.py create mode 100644 langfuse/api/media/client.py create mode 100644 langfuse/api/media/raw_client.py create mode 100644 langfuse/api/media/types/__init__.py create mode 100644 langfuse/api/media/types/get_media_response.py create mode 100644 langfuse/api/media/types/get_media_upload_url_request.py create mode 100644 langfuse/api/media/types/get_media_upload_url_response.py rename langfuse/api/{resources => }/media/types/media_content_type.py (92%) create mode 100644 langfuse/api/media/types/patch_media_body.py create mode 100644 langfuse/api/metrics/__init__.py rename langfuse/api/{resources => }/metrics/client.py (65%) create mode 100644 langfuse/api/metrics/raw_client.py create mode 100644 langfuse/api/metrics/types/__init__.py create mode 100644 langfuse/api/metrics/types/metrics_response.py create mode 100644 langfuse/api/metrics_v2/__init__.py rename langfuse/api/{resources/metrics_v_2 => metrics_v2}/client.py (81%) create mode 100644 langfuse/api/metrics_v2/raw_client.py create mode 100644 langfuse/api/metrics_v2/types/__init__.py create mode 100644 langfuse/api/metrics_v2/types/metrics_v2response.py create mode 100644 langfuse/api/models/__init__.py create mode 100644 langfuse/api/models/client.py create mode 100644 langfuse/api/models/raw_client.py create mode 100644 langfuse/api/models/types/__init__.py rename langfuse/api/{resources => }/models/types/create_model_request.py (54%) create mode 100644 langfuse/api/models/types/paginated_models.py create mode 100644 langfuse/api/observations/__init__.py rename langfuse/api/{resources => }/observations/client.py (67%) create mode 100644 langfuse/api/observations/raw_client.py create mode 100644 langfuse/api/observations/types/__init__.py create mode 100644 langfuse/api/observations/types/observations.py create mode 100644 langfuse/api/observations/types/observations_views.py create mode 100644 langfuse/api/observations_v2/__init__.py create mode 100644 langfuse/api/observations_v2/client.py rename langfuse/api/{resources/observations_v_2/client.py => observations_v2/raw_client.py} (80%) create mode 100644 langfuse/api/observations_v2/types/__init__.py create mode 100644 langfuse/api/observations_v2/types/observations_v2meta.py create mode 100644 langfuse/api/observations_v2/types/observations_v2response.py create mode 100644 langfuse/api/opentelemetry/__init__.py rename langfuse/api/{resources => }/opentelemetry/client.py (69%) create mode 100644 langfuse/api/opentelemetry/raw_client.py create mode 100644 langfuse/api/opentelemetry/types/__init__.py create mode 100644 langfuse/api/opentelemetry/types/otel_attribute.py create mode 100644 langfuse/api/opentelemetry/types/otel_attribute_value.py create mode 100644 langfuse/api/opentelemetry/types/otel_resource.py create mode 100644 langfuse/api/opentelemetry/types/otel_resource_span.py create mode 100644 langfuse/api/opentelemetry/types/otel_scope.py create mode 100644 langfuse/api/opentelemetry/types/otel_scope_span.py create mode 100644 langfuse/api/opentelemetry/types/otel_span.py create mode 100644 langfuse/api/opentelemetry/types/otel_trace_response.py create mode 100644 langfuse/api/organizations/__init__.py create mode 100644 langfuse/api/organizations/client.py create mode 100644 langfuse/api/organizations/raw_client.py create mode 100644 langfuse/api/organizations/types/__init__.py create mode 100644 langfuse/api/organizations/types/delete_membership_request.py create mode 100644 langfuse/api/organizations/types/membership_deletion_response.py create mode 100644 langfuse/api/organizations/types/membership_request.py create mode 100644 langfuse/api/organizations/types/membership_response.py rename langfuse/api/{resources => }/organizations/types/membership_role.py (92%) create mode 100644 langfuse/api/organizations/types/memberships_response.py create mode 100644 langfuse/api/organizations/types/organization_api_key.py create mode 100644 langfuse/api/organizations/types/organization_api_keys_response.py create mode 100644 langfuse/api/organizations/types/organization_project.py create mode 100644 langfuse/api/organizations/types/organization_projects_response.py create mode 100644 langfuse/api/projects/__init__.py create mode 100644 langfuse/api/projects/client.py create mode 100644 langfuse/api/projects/raw_client.py create mode 100644 langfuse/api/projects/types/__init__.py create mode 100644 langfuse/api/projects/types/api_key_deletion_response.py create mode 100644 langfuse/api/projects/types/api_key_list.py create mode 100644 langfuse/api/projects/types/api_key_response.py create mode 100644 langfuse/api/projects/types/api_key_summary.py create mode 100644 langfuse/api/projects/types/organization.py create mode 100644 langfuse/api/projects/types/project.py create mode 100644 langfuse/api/projects/types/project_deletion_response.py create mode 100644 langfuse/api/projects/types/projects.py rename langfuse/api/{resources => }/prompt_version/__init__.py (76%) create mode 100644 langfuse/api/prompt_version/client.py create mode 100644 langfuse/api/prompt_version/raw_client.py create mode 100644 langfuse/api/prompts/__init__.py create mode 100644 langfuse/api/prompts/client.py create mode 100644 langfuse/api/prompts/raw_client.py create mode 100644 langfuse/api/prompts/types/__init__.py create mode 100644 langfuse/api/prompts/types/base_prompt.py create mode 100644 langfuse/api/prompts/types/chat_message.py create mode 100644 langfuse/api/prompts/types/chat_message_type.py create mode 100644 langfuse/api/prompts/types/chat_message_with_placeholders.py create mode 100644 langfuse/api/prompts/types/chat_prompt.py create mode 100644 langfuse/api/prompts/types/create_chat_prompt_request.py create mode 100644 langfuse/api/prompts/types/create_chat_prompt_type.py create mode 100644 langfuse/api/prompts/types/create_prompt_request.py create mode 100644 langfuse/api/prompts/types/create_text_prompt_request.py create mode 100644 langfuse/api/prompts/types/create_text_prompt_type.py create mode 100644 langfuse/api/prompts/types/placeholder_message.py create mode 100644 langfuse/api/prompts/types/placeholder_message_type.py create mode 100644 langfuse/api/prompts/types/prompt.py create mode 100644 langfuse/api/prompts/types/prompt_meta.py create mode 100644 langfuse/api/prompts/types/prompt_meta_list_response.py rename langfuse/api/{resources => }/prompts/types/prompt_type.py (87%) create mode 100644 langfuse/api/prompts/types/text_prompt.py delete mode 100644 langfuse/api/reference.md delete mode 100644 langfuse/api/resources/__init__.py delete mode 100644 langfuse/api/resources/annotation_queues/__init__.py delete mode 100644 langfuse/api/resources/annotation_queues/client.py delete mode 100644 langfuse/api/resources/annotation_queues/types/__init__.py delete mode 100644 langfuse/api/resources/annotation_queues/types/annotation_queue.py delete mode 100644 langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py delete mode 100644 langfuse/api/resources/annotation_queues/types/annotation_queue_item.py delete mode 100644 langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py delete mode 100644 langfuse/api/resources/annotation_queues/types/create_annotation_queue_item_request.py delete mode 100644 langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py delete mode 100644 langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py delete mode 100644 langfuse/api/resources/annotation_queues/types/delete_annotation_queue_item_response.py delete mode 100644 langfuse/api/resources/annotation_queues/types/paginated_annotation_queue_items.py delete mode 100644 langfuse/api/resources/annotation_queues/types/paginated_annotation_queues.py delete mode 100644 langfuse/api/resources/annotation_queues/types/update_annotation_queue_item_request.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/__init__.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/client.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/types/__init__.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py delete mode 100644 langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py delete mode 100644 langfuse/api/resources/comments/__init__.py delete mode 100644 langfuse/api/resources/comments/client.py delete mode 100644 langfuse/api/resources/comments/types/__init__.py delete mode 100644 langfuse/api/resources/comments/types/create_comment_request.py delete mode 100644 langfuse/api/resources/comments/types/create_comment_response.py delete mode 100644 langfuse/api/resources/comments/types/get_comments_response.py delete mode 100644 langfuse/api/resources/commons/__init__.py delete mode 100644 langfuse/api/resources/commons/errors/__init__.py delete mode 100644 langfuse/api/resources/commons/errors/access_denied_error.py delete mode 100644 langfuse/api/resources/commons/errors/error.py delete mode 100644 langfuse/api/resources/commons/errors/method_not_allowed_error.py delete mode 100644 langfuse/api/resources/commons/errors/not_found_error.py delete mode 100644 langfuse/api/resources/commons/errors/unauthorized_error.py delete mode 100644 langfuse/api/resources/commons/types/__init__.py delete mode 100644 langfuse/api/resources/commons/types/base_score.py delete mode 100644 langfuse/api/resources/commons/types/base_score_v_1.py delete mode 100644 langfuse/api/resources/commons/types/boolean_score.py delete mode 100644 langfuse/api/resources/commons/types/boolean_score_v_1.py delete mode 100644 langfuse/api/resources/commons/types/categorical_score.py delete mode 100644 langfuse/api/resources/commons/types/categorical_score_v_1.py delete mode 100644 langfuse/api/resources/commons/types/comment.py delete mode 100644 langfuse/api/resources/commons/types/config_category.py delete mode 100644 langfuse/api/resources/commons/types/correction_score.py delete mode 100644 langfuse/api/resources/commons/types/dataset.py delete mode 100644 langfuse/api/resources/commons/types/dataset_item.py delete mode 100644 langfuse/api/resources/commons/types/dataset_run.py delete mode 100644 langfuse/api/resources/commons/types/dataset_run_item.py delete mode 100644 langfuse/api/resources/commons/types/dataset_run_with_items.py delete mode 100644 langfuse/api/resources/commons/types/model_price.py delete mode 100644 langfuse/api/resources/commons/types/numeric_score.py delete mode 100644 langfuse/api/resources/commons/types/numeric_score_v_1.py delete mode 100644 langfuse/api/resources/commons/types/observation.py delete mode 100644 langfuse/api/resources/commons/types/observations_view.py delete mode 100644 langfuse/api/resources/commons/types/score.py delete mode 100644 langfuse/api/resources/commons/types/score_config.py delete mode 100644 langfuse/api/resources/commons/types/score_v_1.py delete mode 100644 langfuse/api/resources/commons/types/session.py delete mode 100644 langfuse/api/resources/commons/types/session_with_traces.py delete mode 100644 langfuse/api/resources/commons/types/trace.py delete mode 100644 langfuse/api/resources/commons/types/trace_with_details.py delete mode 100644 langfuse/api/resources/commons/types/trace_with_full_details.py delete mode 100644 langfuse/api/resources/commons/types/usage.py delete mode 100644 langfuse/api/resources/dataset_items/__init__.py delete mode 100644 langfuse/api/resources/dataset_items/client.py delete mode 100644 langfuse/api/resources/dataset_items/types/__init__.py delete mode 100644 langfuse/api/resources/dataset_items/types/create_dataset_item_request.py delete mode 100644 langfuse/api/resources/dataset_items/types/delete_dataset_item_response.py delete mode 100644 langfuse/api/resources/dataset_items/types/paginated_dataset_items.py delete mode 100644 langfuse/api/resources/dataset_run_items/__init__.py delete mode 100644 langfuse/api/resources/dataset_run_items/client.py delete mode 100644 langfuse/api/resources/dataset_run_items/types/__init__.py delete mode 100644 langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py delete mode 100644 langfuse/api/resources/dataset_run_items/types/paginated_dataset_run_items.py delete mode 100644 langfuse/api/resources/datasets/__init__.py delete mode 100644 langfuse/api/resources/datasets/client.py delete mode 100644 langfuse/api/resources/datasets/types/__init__.py delete mode 100644 langfuse/api/resources/datasets/types/create_dataset_request.py delete mode 100644 langfuse/api/resources/datasets/types/delete_dataset_run_response.py delete mode 100644 langfuse/api/resources/datasets/types/paginated_dataset_runs.py delete mode 100644 langfuse/api/resources/datasets/types/paginated_datasets.py delete mode 100644 langfuse/api/resources/health/__init__.py delete mode 100644 langfuse/api/resources/health/client.py delete mode 100644 langfuse/api/resources/health/errors/__init__.py delete mode 100644 langfuse/api/resources/health/errors/service_unavailable_error.py delete mode 100644 langfuse/api/resources/health/types/__init__.py delete mode 100644 langfuse/api/resources/health/types/health_response.py delete mode 100644 langfuse/api/resources/ingestion/__init__.py delete mode 100644 langfuse/api/resources/ingestion/types/__init__.py delete mode 100644 langfuse/api/resources/ingestion/types/base_event.py delete mode 100644 langfuse/api/resources/ingestion/types/create_event_body.py delete mode 100644 langfuse/api/resources/ingestion/types/create_event_event.py delete mode 100644 langfuse/api/resources/ingestion/types/create_generation_body.py delete mode 100644 langfuse/api/resources/ingestion/types/create_generation_event.py delete mode 100644 langfuse/api/resources/ingestion/types/create_observation_event.py delete mode 100644 langfuse/api/resources/ingestion/types/create_span_body.py delete mode 100644 langfuse/api/resources/ingestion/types/create_span_event.py delete mode 100644 langfuse/api/resources/ingestion/types/ingestion_error.py delete mode 100644 langfuse/api/resources/ingestion/types/ingestion_event.py delete mode 100644 langfuse/api/resources/ingestion/types/ingestion_response.py delete mode 100644 langfuse/api/resources/ingestion/types/ingestion_success.py delete mode 100644 langfuse/api/resources/ingestion/types/observation_body.py delete mode 100644 langfuse/api/resources/ingestion/types/open_ai_completion_usage_schema.py delete mode 100644 langfuse/api/resources/ingestion/types/open_ai_response_usage_schema.py delete mode 100644 langfuse/api/resources/ingestion/types/open_ai_usage.py delete mode 100644 langfuse/api/resources/ingestion/types/optional_observation_body.py delete mode 100644 langfuse/api/resources/ingestion/types/score_body.py delete mode 100644 langfuse/api/resources/ingestion/types/score_event.py delete mode 100644 langfuse/api/resources/ingestion/types/sdk_log_body.py delete mode 100644 langfuse/api/resources/ingestion/types/sdk_log_event.py delete mode 100644 langfuse/api/resources/ingestion/types/trace_body.py delete mode 100644 langfuse/api/resources/ingestion/types/trace_event.py delete mode 100644 langfuse/api/resources/ingestion/types/update_event_body.py delete mode 100644 langfuse/api/resources/ingestion/types/update_generation_body.py delete mode 100644 langfuse/api/resources/ingestion/types/update_generation_event.py delete mode 100644 langfuse/api/resources/ingestion/types/update_observation_event.py delete mode 100644 langfuse/api/resources/ingestion/types/update_span_body.py delete mode 100644 langfuse/api/resources/ingestion/types/update_span_event.py delete mode 100644 langfuse/api/resources/llm_connections/__init__.py delete mode 100644 langfuse/api/resources/llm_connections/client.py delete mode 100644 langfuse/api/resources/llm_connections/types/__init__.py delete mode 100644 langfuse/api/resources/llm_connections/types/llm_connection.py delete mode 100644 langfuse/api/resources/llm_connections/types/paginated_llm_connections.py delete mode 100644 langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py delete mode 100644 langfuse/api/resources/media/__init__.py delete mode 100644 langfuse/api/resources/media/client.py delete mode 100644 langfuse/api/resources/media/types/__init__.py delete mode 100644 langfuse/api/resources/media/types/get_media_response.py delete mode 100644 langfuse/api/resources/media/types/get_media_upload_url_request.py delete mode 100644 langfuse/api/resources/media/types/get_media_upload_url_response.py delete mode 100644 langfuse/api/resources/media/types/patch_media_body.py delete mode 100644 langfuse/api/resources/metrics/__init__.py delete mode 100644 langfuse/api/resources/metrics/types/__init__.py delete mode 100644 langfuse/api/resources/metrics/types/metrics_response.py delete mode 100644 langfuse/api/resources/metrics_v_2/__init__.py delete mode 100644 langfuse/api/resources/metrics_v_2/types/__init__.py delete mode 100644 langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py delete mode 100644 langfuse/api/resources/models/__init__.py delete mode 100644 langfuse/api/resources/models/client.py delete mode 100644 langfuse/api/resources/models/types/__init__.py delete mode 100644 langfuse/api/resources/models/types/paginated_models.py delete mode 100644 langfuse/api/resources/observations/__init__.py delete mode 100644 langfuse/api/resources/observations/types/__init__.py delete mode 100644 langfuse/api/resources/observations/types/observations.py delete mode 100644 langfuse/api/resources/observations/types/observations_views.py delete mode 100644 langfuse/api/resources/observations_v_2/__init__.py delete mode 100644 langfuse/api/resources/observations_v_2/types/__init__.py delete mode 100644 langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py delete mode 100644 langfuse/api/resources/observations_v_2/types/observations_v_2_response.py delete mode 100644 langfuse/api/resources/opentelemetry/__init__.py delete mode 100644 langfuse/api/resources/opentelemetry/types/__init__.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_attribute.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_attribute_value.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_resource.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_resource_span.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_scope.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_scope_span.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_span.py delete mode 100644 langfuse/api/resources/opentelemetry/types/otel_trace_response.py delete mode 100644 langfuse/api/resources/organizations/__init__.py delete mode 100644 langfuse/api/resources/organizations/client.py delete mode 100644 langfuse/api/resources/organizations/types/__init__.py delete mode 100644 langfuse/api/resources/organizations/types/delete_membership_request.py delete mode 100644 langfuse/api/resources/organizations/types/membership_deletion_response.py delete mode 100644 langfuse/api/resources/organizations/types/membership_request.py delete mode 100644 langfuse/api/resources/organizations/types/membership_response.py delete mode 100644 langfuse/api/resources/organizations/types/memberships_response.py delete mode 100644 langfuse/api/resources/organizations/types/organization_api_key.py delete mode 100644 langfuse/api/resources/organizations/types/organization_api_keys_response.py delete mode 100644 langfuse/api/resources/organizations/types/organization_project.py delete mode 100644 langfuse/api/resources/organizations/types/organization_projects_response.py delete mode 100644 langfuse/api/resources/projects/__init__.py delete mode 100644 langfuse/api/resources/projects/client.py delete mode 100644 langfuse/api/resources/projects/types/__init__.py delete mode 100644 langfuse/api/resources/projects/types/api_key_deletion_response.py delete mode 100644 langfuse/api/resources/projects/types/api_key_list.py delete mode 100644 langfuse/api/resources/projects/types/api_key_response.py delete mode 100644 langfuse/api/resources/projects/types/api_key_summary.py delete mode 100644 langfuse/api/resources/projects/types/organization.py delete mode 100644 langfuse/api/resources/projects/types/project.py delete mode 100644 langfuse/api/resources/projects/types/project_deletion_response.py delete mode 100644 langfuse/api/resources/projects/types/projects.py delete mode 100644 langfuse/api/resources/prompt_version/client.py delete mode 100644 langfuse/api/resources/prompts/__init__.py delete mode 100644 langfuse/api/resources/prompts/client.py delete mode 100644 langfuse/api/resources/prompts/types/__init__.py delete mode 100644 langfuse/api/resources/prompts/types/base_prompt.py delete mode 100644 langfuse/api/resources/prompts/types/chat_message.py delete mode 100644 langfuse/api/resources/prompts/types/chat_message_with_placeholders.py delete mode 100644 langfuse/api/resources/prompts/types/chat_prompt.py delete mode 100644 langfuse/api/resources/prompts/types/create_chat_prompt_request.py delete mode 100644 langfuse/api/resources/prompts/types/create_prompt_request.py delete mode 100644 langfuse/api/resources/prompts/types/create_text_prompt_request.py delete mode 100644 langfuse/api/resources/prompts/types/placeholder_message.py delete mode 100644 langfuse/api/resources/prompts/types/prompt.py delete mode 100644 langfuse/api/resources/prompts/types/prompt_meta.py delete mode 100644 langfuse/api/resources/prompts/types/prompt_meta_list_response.py delete mode 100644 langfuse/api/resources/prompts/types/text_prompt.py delete mode 100644 langfuse/api/resources/scim/__init__.py delete mode 100644 langfuse/api/resources/scim/client.py delete mode 100644 langfuse/api/resources/scim/types/__init__.py delete mode 100644 langfuse/api/resources/scim/types/authentication_scheme.py delete mode 100644 langfuse/api/resources/scim/types/bulk_config.py delete mode 100644 langfuse/api/resources/scim/types/empty_response.py delete mode 100644 langfuse/api/resources/scim/types/filter_config.py delete mode 100644 langfuse/api/resources/scim/types/resource_meta.py delete mode 100644 langfuse/api/resources/scim/types/resource_type.py delete mode 100644 langfuse/api/resources/scim/types/resource_types_response.py delete mode 100644 langfuse/api/resources/scim/types/schema_extension.py delete mode 100644 langfuse/api/resources/scim/types/schema_resource.py delete mode 100644 langfuse/api/resources/scim/types/schemas_response.py delete mode 100644 langfuse/api/resources/scim/types/scim_email.py delete mode 100644 langfuse/api/resources/scim/types/scim_feature_support.py delete mode 100644 langfuse/api/resources/scim/types/scim_name.py delete mode 100644 langfuse/api/resources/scim/types/scim_user.py delete mode 100644 langfuse/api/resources/scim/types/scim_users_list_response.py delete mode 100644 langfuse/api/resources/scim/types/service_provider_config.py delete mode 100644 langfuse/api/resources/scim/types/user_meta.py delete mode 100644 langfuse/api/resources/score/__init__.py delete mode 100644 langfuse/api/resources/score/client.py delete mode 100644 langfuse/api/resources/score/types/__init__.py delete mode 100644 langfuse/api/resources/score/types/create_score_request.py delete mode 100644 langfuse/api/resources/score/types/create_score_response.py delete mode 100644 langfuse/api/resources/score_configs/__init__.py delete mode 100644 langfuse/api/resources/score_configs/client.py delete mode 100644 langfuse/api/resources/score_configs/types/__init__.py delete mode 100644 langfuse/api/resources/score_configs/types/create_score_config_request.py delete mode 100644 langfuse/api/resources/score_configs/types/score_configs.py delete mode 100644 langfuse/api/resources/score_configs/types/update_score_config_request.py delete mode 100644 langfuse/api/resources/score_v_2/__init__.py delete mode 100644 langfuse/api/resources/score_v_2/types/__init__.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_data.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_data_boolean.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_data_categorical.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_data_numeric.py delete mode 100644 langfuse/api/resources/score_v_2/types/get_scores_response_trace_data.py delete mode 100644 langfuse/api/resources/sessions/__init__.py delete mode 100644 langfuse/api/resources/sessions/client.py delete mode 100644 langfuse/api/resources/sessions/types/__init__.py delete mode 100644 langfuse/api/resources/sessions/types/paginated_sessions.py delete mode 100644 langfuse/api/resources/trace/__init__.py delete mode 100644 langfuse/api/resources/trace/types/__init__.py delete mode 100644 langfuse/api/resources/trace/types/delete_trace_response.py delete mode 100644 langfuse/api/resources/trace/types/sort.py delete mode 100644 langfuse/api/resources/trace/types/traces.py delete mode 100644 langfuse/api/resources/utils/__init__.py delete mode 100644 langfuse/api/resources/utils/resources/__init__.py delete mode 100644 langfuse/api/resources/utils/resources/pagination/types/__init__.py delete mode 100644 langfuse/api/resources/utils/resources/pagination/types/meta_response.py create mode 100644 langfuse/api/scim/__init__.py create mode 100644 langfuse/api/scim/client.py create mode 100644 langfuse/api/scim/raw_client.py create mode 100644 langfuse/api/scim/types/__init__.py create mode 100644 langfuse/api/scim/types/authentication_scheme.py create mode 100644 langfuse/api/scim/types/bulk_config.py create mode 100644 langfuse/api/scim/types/empty_response.py create mode 100644 langfuse/api/scim/types/filter_config.py create mode 100644 langfuse/api/scim/types/resource_meta.py create mode 100644 langfuse/api/scim/types/resource_type.py create mode 100644 langfuse/api/scim/types/resource_types_response.py create mode 100644 langfuse/api/scim/types/schema_extension.py create mode 100644 langfuse/api/scim/types/schema_resource.py create mode 100644 langfuse/api/scim/types/schemas_response.py create mode 100644 langfuse/api/scim/types/scim_email.py create mode 100644 langfuse/api/scim/types/scim_feature_support.py create mode 100644 langfuse/api/scim/types/scim_name.py create mode 100644 langfuse/api/scim/types/scim_user.py create mode 100644 langfuse/api/scim/types/scim_users_list_response.py create mode 100644 langfuse/api/scim/types/service_provider_config.py create mode 100644 langfuse/api/scim/types/user_meta.py create mode 100644 langfuse/api/score/__init__.py create mode 100644 langfuse/api/score/client.py create mode 100644 langfuse/api/score/raw_client.py create mode 100644 langfuse/api/score/types/__init__.py create mode 100644 langfuse/api/score/types/create_score_request.py create mode 100644 langfuse/api/score/types/create_score_response.py create mode 100644 langfuse/api/score_configs/__init__.py create mode 100644 langfuse/api/score_configs/client.py create mode 100644 langfuse/api/score_configs/raw_client.py create mode 100644 langfuse/api/score_configs/types/__init__.py create mode 100644 langfuse/api/score_configs/types/create_score_config_request.py create mode 100644 langfuse/api/score_configs/types/score_configs.py create mode 100644 langfuse/api/score_configs/types/update_score_config_request.py create mode 100644 langfuse/api/score_v2/__init__.py create mode 100644 langfuse/api/score_v2/client.py rename langfuse/api/{resources/score_v_2/client.py => score_v2/raw_client.py} (59%) create mode 100644 langfuse/api/score_v2/types/__init__.py create mode 100644 langfuse/api/score_v2/types/get_scores_response.py create mode 100644 langfuse/api/score_v2/types/get_scores_response_data.py create mode 100644 langfuse/api/score_v2/types/get_scores_response_data_boolean.py create mode 100644 langfuse/api/score_v2/types/get_scores_response_data_categorical.py create mode 100644 langfuse/api/score_v2/types/get_scores_response_data_correction.py create mode 100644 langfuse/api/score_v2/types/get_scores_response_data_numeric.py create mode 100644 langfuse/api/score_v2/types/get_scores_response_trace_data.py create mode 100644 langfuse/api/sessions/__init__.py create mode 100644 langfuse/api/sessions/client.py create mode 100644 langfuse/api/sessions/raw_client.py create mode 100644 langfuse/api/sessions/types/__init__.py create mode 100644 langfuse/api/sessions/types/paginated_sessions.py create mode 100644 langfuse/api/trace/__init__.py rename langfuse/api/{resources => }/trace/client.py (61%) create mode 100644 langfuse/api/trace/raw_client.py create mode 100644 langfuse/api/trace/types/__init__.py create mode 100644 langfuse/api/trace/types/delete_trace_response.py create mode 100644 langfuse/api/trace/types/sort.py create mode 100644 langfuse/api/trace/types/traces.py create mode 100644 langfuse/api/utils/__init__.py create mode 100644 langfuse/api/utils/pagination/__init__.py create mode 100644 langfuse/api/utils/pagination/types/__init__.py create mode 100644 langfuse/api/utils/pagination/types/meta_response.py create mode 100644 langfuse/span_filter.py delete mode 100644 tests/test_deprecation.py create mode 100644 tests/test_span_filter.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74013b0b6..9908e5caa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,7 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.14" name: Test on Python version ${{ matrix.python-version }} steps: diff --git a/langfuse/__init__.py b/langfuse/__init__.py index ab4ceedb1..efb9773c0 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -28,6 +28,13 @@ LangfuseSpan, LangfuseTool, ) +from .span_filter import ( + KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES, + is_default_export_span, + is_genai_span, + is_known_llm_instrumentor, + is_langfuse_span, +) Langfuse = _client_module.Langfuse @@ -55,6 +62,11 @@ "EvaluatorStats", "BatchEvaluationResumeToken", "BatchEvaluationResult", + "is_default_export_span", + "is_langfuse_span", + "is_genai_span", + "is_known_llm_instrumentor", + "KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES", "experiment", "api", ] diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py index 343c70cdb..bfe37446f 100644 --- a/langfuse/_client/attributes.py +++ b/langfuse/_client/attributes.py @@ -12,15 +12,16 @@ import json from datetime import datetime -from typing import Any, Dict, List, Literal, Optional, Union +from typing import Any, Dict, Literal, Optional, Union from langfuse._client.constants import ( ObservationTypeGenerationLike, ObservationTypeSpanLike, ) from langfuse._utils.serializer import EventSerializer +from langfuse.api import MapValue from langfuse.model import PromptClient -from langfuse.types import MapValue, SpanLevel +from langfuse.types import SpanLevel class LangfuseOtelSpanAttributes: @@ -73,28 +74,14 @@ class LangfuseOtelSpanAttributes: def create_trace_attributes( *, - name: Optional[str] = None, - user_id: Optional[str] = None, - session_id: Optional[str] = None, - version: Optional[str] = None, - release: Optional[str] = None, input: Optional[Any] = None, output: Optional[Any] = None, - metadata: Optional[Any] = None, - tags: Optional[List[str]] = None, public: Optional[bool] = None, ) -> dict: attributes = { - LangfuseOtelSpanAttributes.TRACE_NAME: name, - LangfuseOtelSpanAttributes.TRACE_USER_ID: user_id, - LangfuseOtelSpanAttributes.TRACE_SESSION_ID: session_id, - LangfuseOtelSpanAttributes.VERSION: version, - LangfuseOtelSpanAttributes.RELEASE: release, LangfuseOtelSpanAttributes.TRACE_INPUT: _serialize(input), LangfuseOtelSpanAttributes.TRACE_OUTPUT: _serialize(output), - LangfuseOtelSpanAttributes.TRACE_TAGS: tags, LangfuseOtelSpanAttributes.TRACE_PUBLIC: public, - **_flatten_and_serialize_metadata(metadata, "trace"), } return {k: v for k, v in attributes.items() if v is not None} diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 3787280c5..21f0ba394 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -28,13 +28,14 @@ import backoff import httpx from opentelemetry import trace as otel_trace_api -from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace import ReadableSpan, TracerProvider from opentelemetry.sdk.trace.id_generator import RandomIdGenerator from opentelemetry.util._decorator import ( _AgnosticContextManager, _agnosticcontextmanager, ) from packaging.version import Version +from typing_extensions import deprecated from langfuse._client.attributes import LangfuseOtelSpanAttributes, _serialize from langfuse._client.constants import ( @@ -45,7 +46,7 @@ ObservationTypeSpanLike, get_observation_types_list, ) -from langfuse._client.datasets import DatasetClient, DatasetItemClient +from langfuse._client.datasets import DatasetClient from langfuse._client.environment_variables import ( LANGFUSE_BASE_URL, LANGFUSE_DEBUG, @@ -78,20 +79,23 @@ from langfuse._utils import _get_timestamp from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache -from langfuse.api.resources.commons.errors.error import Error -from langfuse.api.resources.commons.errors.not_found_error import NotFoundError -from langfuse.api.resources.commons.types import DatasetRunWithItems -from langfuse.api.resources.datasets.types import ( +from langfuse.api import ( + CreateChatPromptRequest, + CreateChatPromptType, + CreateTextPromptRequest, + Dataset, + DatasetItem, + DatasetRunWithItems, + DatasetStatus, DeleteDatasetRunResponse, + Error, + MapValue, + NotFoundError, PaginatedDatasetRuns, -) -from langfuse.api.resources.ingestion.types.score_body import ScoreBody -from langfuse.api.resources.ingestion.types.trace_body import TraceBody -from langfuse.api.resources.prompts.types import ( - CreatePromptRequest_Chat, - CreatePromptRequest_Text, Prompt_Chat, Prompt_Text, + ScoreBody, + TraceBody, ) from langfuse.batch_evaluation import ( BatchEvaluationResult, @@ -118,13 +122,6 @@ ChatMessageDict, ChatMessageWithPlaceholdersDict, ChatPromptClient, - CreateDatasetItemRequest, - CreateDatasetRequest, - CreateDatasetRunItemRequest, - Dataset, - DatasetItem, - DatasetStatus, - MapValue, PromptClient, TextPromptClient, ) @@ -166,7 +163,20 @@ class Langfuse: media_upload_thread_count (Optional[int]): Number of background threads for handling media uploads. Defaults to 1. Can also be set via LANGFUSE_MEDIA_UPLOAD_THREAD_COUNT environment variable. sample_rate (Optional[float]): Sampling rate for traces (0.0 to 1.0). Defaults to 1.0 (100% of traces are sampled). Can also be set via LANGFUSE_SAMPLE_RATE environment variable. mask (Optional[MaskFunction]): Function to mask sensitive data in traces before sending to the API. - blocked_instrumentation_scopes (Optional[List[str]]): List of instrumentation scope names to block from being exported to Langfuse. Spans from these scopes will be filtered out before being sent to the API. Useful for filtering out spans from specific libraries or frameworks. For exported spans, you can see the instrumentation scope name in the span metadata in Langfuse (`metadata.scope.name`) + blocked_instrumentation_scopes (Optional[List[str]]): Deprecated. Use `should_export_span` instead. Equivalent behavior: + ```python + from langfuse.span_filter import is_default_export_span + blocked = {"sqlite", "requests"} + + should_export_span = lambda span: ( + is_default_export_span(span) + and ( + span.instrumentation_scope is None + or span.instrumentation_scope.name not in blocked + ) + ) + ``` + should_export_span (Optional[Callable[[ReadableSpan], bool]]): Callback to decide whether to export a span. If omitted, Langfuse uses the default filter (Langfuse SDK spans, spans with `gen_ai.*` attributes, and known LLM instrumentation scopes). additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. tracer_provider(Optional[TracerProvider]): OpenTelemetry TracerProvider to use for Langfuse. This can be useful to set to have disconnected tracing between Langfuse and other OpenTelemetry-span emitting libraries. Note: To track active spans, the context is still shared between TracerProviders. This may lead to broken trace trees. @@ -182,7 +192,7 @@ class Langfuse: ) # Create a trace span - with langfuse.start_as_current_span(name="process-query") as span: + with langfuse.start_as_current_observation(name="process-query") as span: # Your application code here # Create a nested generation span for an LLM call @@ -229,6 +239,7 @@ def __init__( sample_rate: Optional[float] = None, mask: Optional[MaskFunction] = None, blocked_instrumentation_scopes: Optional[List[str]] = None, + should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, ): @@ -291,6 +302,18 @@ def __init__( "OTEL_SDK_DISABLED is set. Langfuse tracing will be disabled and no traces will appear in the UI." ) + if blocked_instrumentation_scopes is not None: + warnings.warn( + "`blocked_instrumentation_scopes` is deprecated and will be removed in a future release. " + "Use `should_export_span` instead. Example: " + "from langfuse.span_filter import is_default_export_span; " + 'blocked={"scope"}; should_export_span=lambda span: ' + "is_default_export_span(span) and (span.instrumentation_scope is None or " + "span.instrumentation_scope.name not in blocked).", + DeprecationWarning, + stacklevel=2, + ) + # Initialize api and tracer if requirements are met self._resources = LangfuseResourceManager( public_key=public_key, @@ -307,6 +330,7 @@ def __init__( mask=mask, tracing_enabled=self._tracing_enabled, blocked_instrumentation_scopes=blocked_instrumentation_scopes, + should_export_span=should_export_span, additional_headers=additional_headers, tracer_provider=tracer_provider, ) @@ -320,121 +344,6 @@ def __init__( self.api = self._resources.api self.async_api = self._resources.async_api - def start_span( - self, - *, - trace_context: Optional[TraceContext] = None, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - ) -> LangfuseSpan: - """Create a new span for tracing a unit of work. - - This method creates a new span but does not set it as the current span in the - context. To create and use a span within a context, use start_as_current_span(). - - The created span will be the child of the current span in the context. - - Args: - trace_context: Optional context for connecting to an existing trace - name: Name of the span (e.g., function or operation name) - input: Input data for the operation (can be any JSON-serializable object) - output: Output data from the operation (can be any JSON-serializable object) - metadata: Additional metadata to associate with the span - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - - Returns: - A LangfuseSpan object that must be ended with .end() when the operation completes - - Example: - ```python - span = langfuse.start_span(name="process-data") - try: - # Do work - span.update(output="result") - finally: - span.end() - ``` - """ - return self.start_observation( - trace_context=trace_context, - name=name, - as_type="span", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ) - - def start_as_current_span( - self, - *, - trace_context: Optional[TraceContext] = None, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - end_on_exit: Optional[bool] = None, - ) -> _AgnosticContextManager[LangfuseSpan]: - """Create a new span and set it as the current span in a context manager. - - This method creates a new span and sets it as the current span within a context - manager. Use this method with a 'with' statement to automatically handle span - lifecycle within a code block. - - The created span will be the child of the current span in the context. - - Args: - trace_context: Optional context for connecting to an existing trace - name: Name of the span (e.g., function or operation name) - input: Input data for the operation (can be any JSON-serializable object) - output: Output data from the operation (can be any JSON-serializable object) - metadata: Additional metadata to associate with the span - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks. - - Returns: - A context manager that yields a LangfuseSpan - - Example: - ```python - with langfuse.start_as_current_span(name="process-query") as span: - # Do work - result = process_data() - span.update(output=result) - - # Create a child span automatically - with span.start_as_current_span(name="sub-operation") as child_span: - # Do sub-operation work - child_span.update(output="sub-result") - ``` - """ - return self.start_as_current_observation( - trace_context=trace_context, - name=name, - as_type="span", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - end_on_exit=end_on_exit, - ) - @overload def start_observation( self, @@ -757,196 +666,6 @@ def _create_observation_from_otel_span( # span._otel_span.set_attribute("langfuse.observation.type", as_type) # return span - def start_generation( - self, - *, - trace_context: Optional[TraceContext] = None, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - completion_start_time: Optional[datetime] = None, - model: Optional[str] = None, - model_parameters: Optional[Dict[str, MapValue]] = None, - usage_details: Optional[Dict[str, int]] = None, - cost_details: Optional[Dict[str, float]] = None, - prompt: Optional[PromptClient] = None, - ) -> LangfuseGeneration: - """Create a new generation span for model generations. - - DEPRECATED: This method is deprecated and will be removed in a future version. - Use start_observation(as_type='generation') instead. - - This method creates a specialized span for tracking model generations. - It includes additional fields specific to model generations such as model name, - token usage, and cost details. - - The created generation span will be the child of the current span in the context. - - Args: - trace_context: Optional context for connecting to an existing trace - name: Name of the generation operation - input: Input data for the model (e.g., prompts) - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management - - Returns: - A LangfuseGeneration object that must be ended with .end() when complete - - Example: - ```python - generation = langfuse.start_generation( - name="answer-generation", - model="gpt-4", - input={"prompt": "Explain quantum computing"}, - model_parameters={"temperature": 0.7} - ) - try: - # Call model API - response = llm.generate(...) - - generation.update( - output=response.text, - usage_details={ - "prompt_tokens": response.usage.prompt_tokens, - "completion_tokens": response.usage.completion_tokens - } - ) - finally: - generation.end() - ``` - """ - warnings.warn( - "start_generation is deprecated and will be removed in a future version. " - "Use start_observation(as_type='generation') instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.start_observation( - trace_context=trace_context, - name=name, - as_type="generation", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ) - - def start_as_current_generation( - self, - *, - trace_context: Optional[TraceContext] = None, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - completion_start_time: Optional[datetime] = None, - model: Optional[str] = None, - model_parameters: Optional[Dict[str, MapValue]] = None, - usage_details: Optional[Dict[str, int]] = None, - cost_details: Optional[Dict[str, float]] = None, - prompt: Optional[PromptClient] = None, - end_on_exit: Optional[bool] = None, - ) -> _AgnosticContextManager[LangfuseGeneration]: - """Create a new generation span and set it as the current span in a context manager. - - DEPRECATED: This method is deprecated and will be removed in a future version. - Use start_as_current_observation(as_type='generation') instead. - - This method creates a specialized span for model generations and sets it as the - current span within a context manager. Use this method with a 'with' statement to - automatically handle the generation span lifecycle within a code block. - - The created generation span will be the child of the current span in the context. - - Args: - trace_context: Optional context for connecting to an existing trace - name: Name of the generation operation - input: Input data for the model (e.g., prompts) - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management - end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks. - - Returns: - A context manager that yields a LangfuseGeneration - - Example: - ```python - with langfuse.start_as_current_generation( - name="answer-generation", - model="gpt-4", - input={"prompt": "Explain quantum computing"} - ) as generation: - # Call model API - response = llm.generate(...) - - # Update with results - generation.update( - output=response.text, - usage_details={ - "prompt_tokens": response.usage.prompt_tokens, - "completion_tokens": response.usage.completion_tokens - } - ) - ``` - """ - warnings.warn( - "start_as_current_generation is deprecated and will be removed in a future version. " - "Use start_as_current_observation(as_type='generation') instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.start_as_current_observation( - trace_context=trace_context, - name=name, - as_type="generation", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - end_on_exit=end_on_exit, - ) - @overload def start_as_current_observation( self, @@ -1173,7 +892,7 @@ def start_as_current_observation( span.update(output=result) # Create a child span automatically - with span.start_as_current_span(name="sub-operation") as child_span: + with span.start_as_current_observation(name="sub-operation") as child_span: # Do sub-operation work child_span.update(output="sub-result") @@ -1481,7 +1200,7 @@ def _get_current_otel_span(self) -> Optional[otel_trace_api.Span]: if current_span is otel_trace_api.INVALID_SPAN: langfuse_logger.warning( "Context error: No active span in current context. Operations that depend on an active span will be skipped. " - "Ensure spans are created with start_as_current_span() or that you're operating within an active span context." + "Ensure spans are created with start_as_current_observation() or that you're operating within an active span context." ) return None @@ -1600,7 +1319,7 @@ def update_current_span( Example: ```python - with langfuse.start_as_current_span(name="process-data") as span: + with langfuse.start_as_current_observation(name="process-data") as span: # Initial processing result = process_first_part() @@ -1641,38 +1360,34 @@ def update_current_span( status_message=status_message, ) - def update_current_trace( + @deprecated( + "Trace-level input/output is deprecated. " + "For trace attributes (user_id, session_id, tags, etc.), use propagate_attributes() instead. " + "This method will be removed in a future major version." + ) + def set_current_trace_io( self, *, - name: Optional[str] = None, - user_id: Optional[str] = None, - session_id: Optional[str] = None, - version: Optional[str] = None, input: Optional[Any] = None, output: Optional[Any] = None, - metadata: Optional[Any] = None, - tags: Optional[List[str]] = None, - public: Optional[bool] = None, ) -> None: - """Update the current trace with additional information. + """Set trace-level input and output for the current span's trace. + + .. deprecated:: + This is a legacy method for backward compatibility with Langfuse platform + features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge + evaluators). It will be removed in a future major version. + + For setting other trace attributes (user_id, session_id, metadata, tags, version), + use :meth:`propagate_attributes` instead. Args: - name: Updated name for the Langfuse trace - user_id: ID of the user who initiated the Langfuse trace - session_id: Session identifier for grouping related Langfuse traces - version: Version identifier for the application or service - input: Input data for the overall Langfuse trace - output: Output data from the overall Langfuse trace - metadata: Additional metadata to associate with the Langfuse trace - tags: List of tags to categorize the Langfuse trace - public: Whether the Langfuse trace should be publicly accessible - - See Also: - :func:`langfuse.propagate_attributes`: Recommended replacement + input: Input data to associate with the trace. + output: Output data to associate with the trace. """ if not self._tracing_enabled: langfuse_logger.debug( - "Operation skipped: update_current_trace - Tracing is disabled or client is in no-op mode." + "Operation skipped: set_current_trace_io - Tracing is disabled or client is in no-op mode." ) return @@ -1690,18 +1405,44 @@ def update_current_trace( environment=self._environment, ) - span.update_trace( - name=name, - user_id=user_id, - session_id=session_id, - version=version, + span.set_trace_io( input=input, output=output, - metadata=metadata, - tags=tags, - public=public, ) + def set_current_trace_as_public(self) -> None: + """Make the current trace publicly accessible via its URL. + + When a trace is published, anyone with the trace link can view the full trace + without needing to be logged in to Langfuse. This action cannot be undone + programmatically - once published, the entire trace becomes public. + + This is a convenience method that publishes the trace from the currently + active span context. Use this when you want to make a trace public from + within a traced function without needing direct access to the span object. + """ + if not self._tracing_enabled: + langfuse_logger.debug( + "Operation skipped: set_current_trace_as_public - Tracing is disabled or client is in no-op mode." + ) + return + + current_otel_span = self._get_current_otel_span() + + if current_otel_span is not None and current_otel_span.is_recording(): + existing_observation_type = current_otel_span.attributes.get( # type: ignore[attr-defined] + LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "span" + ) + # We need to preserve the class to keep the correct observation type + span_class = self._get_span_class(existing_observation_type) + span = span_class( + otel_span=current_otel_span, + langfuse_client=self, + environment=self._environment, + ) + + span.set_trace_as_public() + def create_event( self, *, @@ -1908,7 +1649,7 @@ def create_trace_id(*, seed: Optional[str] = None) -> str: correlated_trace_id = langfuse.create_trace_id(seed=external_id) # Use the ID with trace context - with langfuse.start_as_current_span( + with langfuse.start_as_current_observation( name="process-request", trace_context={"trace_id": trace_id} ) as span: @@ -2063,7 +1804,7 @@ def create_score( try: new_body = ScoreBody( id=score_id, - sessionId=session_id, + session_id=session_id, datasetRunId=dataset_run_id, traceId=trace_id, observationId=observation_id, @@ -2276,7 +2017,7 @@ def score_current_trace( Example: ```python - with langfuse.start_as_current_span(name="process-user-request") as span: + with langfuse.start_as_current_observation(name="process-user-request") as span: # Process request result = process_complete_request() span.update(output=result) @@ -2321,7 +2062,7 @@ def flush(self) -> None: Example: ```python # Record some spans and scores - with langfuse.start_as_current_span(name="operation") as span: + with langfuse.start_as_current_observation(name="operation") as span: # Do work... pass @@ -2372,7 +2113,7 @@ def get_current_trace_id(self) -> Optional[str]: Example: ```python - with langfuse.start_as_current_span(name="process-request") as span: + with langfuse.start_as_current_observation(name="process-request") as span: # Get the current trace ID for reference trace_id = langfuse.get_current_trace_id() @@ -2406,7 +2147,7 @@ def get_current_observation_id(self) -> Optional[str]: Example: ```python - with langfuse.start_as_current_span(name="process-user-query") as span: + with langfuse.start_as_current_observation(name="process-user-query") as span: # Get the current observation ID observation_id = langfuse.get_current_observation_id() @@ -2454,7 +2195,7 @@ def get_trace_url(self, *, trace_id: Optional[str] = None) -> Optional[str]: Example: ```python # Get URL for the current trace - with langfuse.start_as_current_span(name="process-request") as span: + with langfuse.start_as_current_observation(name="process-request") as span: trace_url = langfuse.get_trace_url() log.info(f"Processing trace: {trace_url}") @@ -2515,9 +2256,12 @@ def get_dataset( page += 1 - items = [DatasetItemClient(i, langfuse=self) for i in dataset_items] - - return DatasetClient(dataset, items=items, version=version) + return DatasetClient( + dataset=dataset, + items=dataset_items, + version=version, + langfuse_client=self, + ) except Error as e: handle_fern_exception(e) @@ -2536,10 +2280,13 @@ def get_dataset_run( DatasetRunWithItems: The dataset run with its items. """ try: - return self.api.datasets.get_run( - dataset_name=self._url_encode(dataset_name), - run_name=self._url_encode(run_name), - request_options=None, + return cast( + DatasetRunWithItems, + self.api.datasets.get_run( + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), + request_options=None, + ), ) except Error as e: handle_fern_exception(e) @@ -2563,11 +2310,14 @@ def get_dataset_runs( PaginatedDatasetRuns: Paginated list of dataset runs. """ try: - return self.api.datasets.get_runs( - dataset_name=self._url_encode(dataset_name), - page=page, - limit=limit, - request_options=None, + return cast( + PaginatedDatasetRuns, + self.api.datasets.get_runs( + dataset_name=self._url_encode(dataset_name), + page=page, + limit=limit, + request_options=None, + ), ) except Error as e: handle_fern_exception(e) @@ -2586,10 +2336,13 @@ def delete_dataset_run( DeleteDatasetRunResponse: Confirmation of deletion. """ try: - return self.api.datasets.delete_run( - dataset_name=self._url_encode(dataset_name), - run_name=self._url_encode(run_name), - request_options=None, + return cast( + DeleteDatasetRunResponse, + self.api.datasets.delete_run( + dataset_name=self._url_encode(dataset_name), + run_name=self._url_encode(run_name), + request_options=None, + ), ) except Error as e: handle_fern_exception(e) @@ -2916,7 +2669,7 @@ async def _process_experiment_item( ) -> ExperimentItemResult: span_name = "experiment-item-run" - with self.start_as_current_span(name=span_name) as span: + with self.start_as_current_observation(name=span_name) as span: try: input_data = ( item.get("input") @@ -2957,15 +2710,13 @@ async def _process_experiment_item( # creates multiple event loops across different threads dataset_run_item = await asyncio.to_thread( self.api.dataset_run_items.create, - request=CreateDatasetRunItemRequest( - runName=experiment_run_name, - runDescription=experiment_description, - metadata=experiment_metadata, - datasetItemId=item.id, # type: ignore - traceId=trace_id, - observationId=span.id, - datasetVersion=dataset_version, - ).dict(exclude_none=True), + run_name=experiment_run_name, + run_description=experiment_description, + metadata=experiment_metadata, + dataset_item_id=item.id, # type: ignore + trace_id=trace_id, + observation_id=span.id, + dataset_version=dataset_version, ) dataset_run_id = dataset_run_item.dataset_run_id @@ -3424,16 +3175,17 @@ def create_dataset( Dataset: The created dataset as returned by the Langfuse API. """ try: - body = CreateDatasetRequest( + langfuse_logger.debug(f"Creating datasets {name}") + + result = self.api.datasets.create( name=name, description=description, metadata=metadata, - inputSchema=input_schema, - expectedOutputSchema=expected_output_schema, + input_schema=input_schema, + expected_output_schema=expected_output_schema, ) - langfuse_logger.debug(f"Creating datasets {body}") - return self.api.datasets.create(request=body) + return cast(Dataset, result) except Error as e: handle_fern_exception(e) @@ -3484,18 +3236,20 @@ def create_dataset_item( ``` """ try: - body = CreateDatasetItemRequest( - datasetName=dataset_name, + langfuse_logger.debug(f"Creating dataset item for dataset {dataset_name}") + + result = self.api.dataset_items.create( + dataset_name=dataset_name, input=input, - expectedOutput=expected_output, + expected_output=expected_output, metadata=metadata, - sourceTraceId=source_trace_id, - sourceObservationId=source_observation_id, + source_trace_id=source_trace_id, + source_observation_id=source_observation_id, status=status, id=id, ) - langfuse_logger.debug(f"Creating dataset item {body}") - return self.api.dataset_items.create(request=body) + + return cast(DatasetItem, result) except Error as e: handle_fern_exception(e) raise e @@ -3857,15 +3611,15 @@ def create_prompt( raise ValueError( "For 'chat' type, 'prompt' must be a list of chat messages with role and content attributes." ) - request: Union[CreatePromptRequest_Chat, CreatePromptRequest_Text] = ( - CreatePromptRequest_Chat( + request: Union[CreateChatPromptRequest, CreateTextPromptRequest] = ( + CreateChatPromptRequest( name=name, prompt=cast(Any, prompt), labels=labels, tags=tags, config=config or {}, - commitMessage=commit_message, - type="chat", + commit_message=commit_message, + type=CreateChatPromptType.CHAT, ) ) server_prompt = self.api.prompts.create(request=request) @@ -3878,14 +3632,13 @@ def create_prompt( if not isinstance(prompt, str): raise ValueError("For 'text' type, 'prompt' must be a string.") - request = CreatePromptRequest_Text( + request = CreateTextPromptRequest( name=name, prompt=prompt, labels=labels, tags=tags, config=config or {}, - commitMessage=commit_message, - type="text", + commit_message=commit_message, ) server_prompt = self.api.prompts.create(request=request) diff --git a/langfuse/_client/datasets.py b/langfuse/_client/datasets.py index 7879fbea9..e28ddbebf 100644 --- a/langfuse/_client/datasets.py +++ b/langfuse/_client/datasets.py @@ -1,9 +1,10 @@ import datetime as dt -import logging -from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional - -from opentelemetry.util._decorator import _agnosticcontextmanager +from typing import TYPE_CHECKING, Any, Dict, List, Optional +from langfuse.api import ( + Dataset, + DatasetItem, +) from langfuse.batch_evaluation import CompositeEvaluatorFunction from langfuse.experiment import ( EvaluatorFunction, @@ -11,138 +12,11 @@ RunEvaluatorFunction, TaskFunction, ) -from langfuse.model import ( - CreateDatasetRunItemRequest, - Dataset, - DatasetItem, - DatasetStatus, -) - -from .span import LangfuseSpan if TYPE_CHECKING: from langfuse._client.client import Langfuse -class DatasetItemClient: - """Class for managing dataset items in Langfuse. - - Args: - id (str): Unique identifier of the dataset item. - status (DatasetStatus): The status of the dataset item. Can be either 'ACTIVE' or 'ARCHIVED'. - input (Any): Input data of the dataset item. - expected_output (Optional[Any]): Expected output of the dataset item. - metadata (Optional[Any]): Additional metadata of the dataset item. - source_trace_id (Optional[str]): Identifier of the source trace. - source_observation_id (Optional[str]): Identifier of the source observation. - dataset_id (str): Identifier of the dataset to which this item belongs. - dataset_name (str): Name of the dataset to which this item belongs. - created_at (datetime): Timestamp of dataset item creation. - updated_at (datetime): Timestamp of the last update to the dataset item. - langfuse (Langfuse): Instance of Langfuse client for API interactions. - - Example: - ```python - from langfuse import Langfuse - - langfuse = Langfuse() - - dataset = langfuse.get_dataset("") - - for item in dataset.items: - # Generate a completion using the input of every item - completion, generation = llm_app.run(item.input) - - # Evaluate the completion - generation.score( - name="example-score", - value=1 - ) - ``` - """ - - log = logging.getLogger("langfuse") - - id: str - status: DatasetStatus - input: Any - expected_output: Optional[Any] - metadata: Optional[Any] - source_trace_id: Optional[str] - source_observation_id: Optional[str] - dataset_id: str - dataset_name: str - created_at: dt.datetime - updated_at: dt.datetime - - langfuse: "Langfuse" - - def __init__(self, dataset_item: DatasetItem, langfuse: "Langfuse"): - """Initialize the DatasetItemClient.""" - self.id = dataset_item.id - self.status = dataset_item.status - self.input = dataset_item.input - self.expected_output = dataset_item.expected_output - self.metadata = dataset_item.metadata - self.source_trace_id = dataset_item.source_trace_id - self.source_observation_id = dataset_item.source_observation_id - self.dataset_id = dataset_item.dataset_id - self.dataset_name = dataset_item.dataset_name - self.created_at = dataset_item.created_at - self.updated_at = dataset_item.updated_at - - self.langfuse = langfuse - - @_agnosticcontextmanager - def run( - self, - *, - run_name: str, - run_metadata: Optional[Any] = None, - run_description: Optional[str] = None, - ) -> Generator[LangfuseSpan, None, None]: - """Create a context manager for the dataset item run that links the execution to a Langfuse trace. - - This method is a context manager that creates a trace for the dataset run and yields a span - that can be used to track the execution of the run. - - Args: - run_name (str): The name of the dataset run. - run_metadata (Optional[Any]): Additional metadata to include in dataset run. - run_description (Optional[str]): Description of the dataset run. - - Yields: - span: A LangfuseSpan that can be used to trace the execution of the run. - """ - trace_name = f"Dataset run: {run_name}" - - with self.langfuse.start_as_current_span(name=trace_name) as span: - span.update_trace( - name=trace_name, - metadata={ - "dataset_item_id": self.id, - "run_name": run_name, - "dataset_id": self.dataset_id, - }, - ) - - self.log.debug( - f"Creating dataset run item: run_name={run_name} id={self.id} trace_id={span.trace_id}" - ) - - self.langfuse.api.dataset_run_items.create( - request=CreateDatasetRunItemRequest( - runName=run_name, - datasetItemId=self.id, - traceId=span.trace_id, - metadata=run_metadata, - runDescription=run_description, - ) - ) - - yield span - - class DatasetClient: """Class for managing datasets in Langfuse. @@ -154,7 +28,7 @@ class DatasetClient: project_id (str): Identifier of the project to which the dataset belongs. created_at (datetime): Timestamp of dataset creation. updated_at (datetime): Timestamp of the last update to the dataset. - items (List[DatasetItemClient]): List of dataset items associated with the dataset. + items (List[DatasetItem]): List of dataset items associated with the dataset. version (Optional[datetime]): Timestamp of the dataset version. Example: Print the input of each dataset item in a dataset. @@ -177,13 +51,17 @@ class DatasetClient: metadata: Optional[Any] created_at: dt.datetime updated_at: dt.datetime - items: List[DatasetItemClient] + input_schema: Optional[Any] + expected_output_schema: Optional[Any] + items: List[DatasetItem] version: Optional[dt.datetime] def __init__( self, + *, dataset: Dataset, - items: List[DatasetItemClient], + items: List[DatasetItem], + langfuse_client: "Langfuse", version: Optional[dt.datetime] = None, ): """Initialize the DatasetClient.""" @@ -194,15 +72,11 @@ def __init__( self.metadata = dataset.metadata self.created_at = dataset.created_at self.updated_at = dataset.updated_at + self.input_schema = dataset.input_schema + self.expected_output_schema = dataset.expected_output_schema self.items = items self.version = version - self._langfuse: Optional["Langfuse"] = None - - def _get_langfuse_client(self) -> Optional["Langfuse"]: - """Get the Langfuse client from the first item.""" - if self._langfuse is None and self.items: - self._langfuse = self.items[0].langfuse - return self._langfuse + self._langfuse_client: "Langfuse" = langfuse_client def run_experiment( self, @@ -413,11 +287,7 @@ def content_diversity(*, item_results, **kwargs): - This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.) - Async execution is handled automatically with smart event loop detection """ - langfuse_client = self._get_langfuse_client() - if not langfuse_client: - raise ValueError("No Langfuse client available. Dataset items are empty.") - - return langfuse_client.run_experiment( + return self._langfuse_client.run_experiment( name=name, run_name=run_name, description=description, diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index 9c426d9b3..dd2ee4a29 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -51,6 +51,7 @@ def _create_client_from_instance( sample_rate=instance.sample_rate, mask=instance.mask, blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes, + should_export_span=instance.should_export_span, additional_headers=instance.additional_headers, tracer_provider=instance.tracer_provider, httpx_client=instance.httpx_client, diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 0baca7e87..e0c68db00 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -132,14 +132,14 @@ def propagate_attributes( langfuse = Langfuse() # Set attributes early in the trace - with langfuse.start_as_current_span(name="user_workflow") as span: + with langfuse.start_as_current_observation(name="user_workflow") as span: with langfuse.propagate_attributes( user_id="user_123", session_id="session_abc", metadata={"experiment": "variant_a", "environment": "production"} ): # All spans created here will have user_id, session_id, and metadata - with langfuse.start_span(name="llm_call") as llm_span: + with langfuse.start_observation(name="llm_call") as llm_span: # This span inherits: user_id, session_id, experiment, environment ... @@ -151,15 +151,15 @@ def propagate_attributes( Late propagation (anti-pattern): ```python - with langfuse.start_as_current_span(name="workflow") as span: + with langfuse.start_as_current_observation(name="workflow") as span: # These spans WON'T have user_id - early_span = langfuse.start_span(name="early_work") + early_span = langfuse.start_observation(name="early_work") early_span.end() # Set attributes in the middle with langfuse.propagate_attributes(user_id="user_123"): # Only spans created AFTER this point will have user_id - late_span = langfuse.start_span(name="late_work") + late_span = langfuse.start_observation(name="late_work") late_span.end() # Result: Aggregations by user_id will miss "early_work" span @@ -169,7 +169,7 @@ def propagate_attributes( ```python # Service A - originating service - with langfuse.start_as_current_span(name="api_request"): + with langfuse.start_as_current_observation(name="api_request"): with langfuse.propagate_attributes( user_id="user_123", session_id="session_abc", diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 7422bc76e..45c90ad66 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -18,12 +18,12 @@ import os import threading from queue import Full, Queue -from typing import Any, Dict, List, Optional, cast +from typing import Any, Callable, Dict, List, Optional, cast import httpx from opentelemetry import trace as otel_trace_api from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace import ReadableSpan, TracerProvider from opentelemetry.sdk.trace.sampling import Decision, TraceIdRatioBased from opentelemetry.trace import Tracer @@ -42,7 +42,7 @@ from langfuse._utils.environment import get_common_release_envs from langfuse._utils.prompt_cache import PromptCache from langfuse._utils.request import LangfuseClient -from langfuse.api.client import AsyncFernLangfuse, FernLangfuse +from langfuse.api import AsyncLangfuseAPI, LangfuseAPI from langfuse.logger import langfuse_logger from langfuse.types import MaskFunction @@ -95,6 +95,7 @@ def __new__( mask: Optional[MaskFunction] = None, tracing_enabled: Optional[bool] = None, blocked_instrumentation_scopes: Optional[List[str]] = None, + should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, ) -> "LangfuseResourceManager": @@ -129,6 +130,7 @@ def __new__( if tracing_enabled is not None else True, blocked_instrumentation_scopes=blocked_instrumentation_scopes, + should_export_span=should_export_span, additional_headers=additional_headers, tracer_provider=tracer_provider, ) @@ -154,6 +156,7 @@ def _initialize_instance( mask: Optional[MaskFunction] = None, tracing_enabled: bool = True, blocked_instrumentation_scopes: Optional[List[str]] = None, + should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, ) -> None: @@ -172,6 +175,7 @@ def _initialize_instance( self.media_upload_thread_count = media_upload_thread_count self.sample_rate = sample_rate self.blocked_instrumentation_scopes = blocked_instrumentation_scopes + self.should_export_span = should_export_span self.additional_headers = additional_headers self.tracer_provider: Optional[TracerProvider] = None @@ -190,6 +194,7 @@ def _initialize_instance( flush_at=flush_at, flush_interval=flush_interval, blocked_instrumentation_scopes=blocked_instrumentation_scopes, + should_export_span=should_export_span, additional_headers=additional_headers, ) tracer_provider.add_span_processor(langfuse_processor) @@ -213,7 +218,7 @@ def _initialize_instance( client_headers = additional_headers if additional_headers else {} self.httpx_client = httpx.Client(timeout=timeout, headers=client_headers) - self.api = FernLangfuse( + self.api = LangfuseAPI( base_url=base_url, username=self.public_key, password=secret_key, @@ -223,7 +228,7 @@ def _initialize_instance( httpx_client=self.httpx_client, timeout=timeout, ) - self.async_api = AsyncFernLangfuse( + self.async_api = AsyncLangfuseAPI( base_url=base_url, username=self.public_key, password=secret_key, diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 92c1556ca..a86b8c14a 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -13,14 +13,12 @@ and scoring integration specific to Langfuse's observability platform. """ -import warnings from datetime import datetime from time import time_ns from typing import ( TYPE_CHECKING, Any, Dict, - List, Literal, Optional, Type, @@ -38,6 +36,8 @@ if TYPE_CHECKING: from langfuse._client.client import Langfuse +from typing_extensions import deprecated + from langfuse._client.attributes import ( LangfuseOtelSpanAttributes, create_generation_attributes, @@ -51,8 +51,9 @@ ObservationTypeSpanLike, get_observation_types_list, ) +from langfuse.api import MapValue, ScoreDataType from langfuse.logger import langfuse_logger -from langfuse.types import MapValue, ScoreDataType, SpanLevel +from langfuse.types import SpanLevel # Factory mapping for observation classes # Note: "event" is handled separately due to special instantiation logic @@ -208,34 +209,33 @@ def end(self, *, end_time: Optional[int] = None) -> "LangfuseObservationWrapper" return self - def update_trace( + @deprecated( + "Trace-level input/output is deprecated. " + "For trace attributes (user_id, session_id, tags, etc.), use propagate_attributes() instead. " + "This method will be removed in a future major version." + ) + def set_trace_io( self, *, - name: Optional[str] = None, - user_id: Optional[str] = None, - session_id: Optional[str] = None, - version: Optional[str] = None, input: Optional[Any] = None, output: Optional[Any] = None, - metadata: Optional[Any] = None, - tags: Optional[List[str]] = None, - public: Optional[bool] = None, ) -> "LangfuseObservationWrapper": - """Update the trace that this span belongs to. + """Set trace-level input and output for the trace this span belongs to. + + .. deprecated:: + This is a legacy method for backward compatibility with Langfuse platform + features that still rely on trace-level input/output (e.g., legacy LLM-as-a-judge + evaluators). It will be removed in a future major version. + + For setting other trace attributes (user_id, session_id, metadata, tags, version), + use :meth:`Langfuse.propagate_attributes` instead. Args: - name: Updated name for the trace - user_id: ID of the user who initiated the trace - session_id: Session identifier for grouping related traces - version: Version identifier for the application or service - input: Input data for the overall trace - output: Output data from the overall trace - metadata: Additional metadata to associate with the trace - tags: List of tags to categorize the trace - public: Whether the trace should be publicly accessible - - See Also: - :func:`langfuse.propagate_attributes`: Recommended replacement + input: Input data to associate with the trace. + output: Output data to associate with the trace. + + Returns: + The span instance for method chaining. """ if not self._otel_span.is_recording(): return self @@ -246,26 +246,36 @@ def update_trace( media_processed_output = self._process_media_and_apply_mask( data=output, field="output", span=self._otel_span ) - media_processed_metadata = self._process_media_and_apply_mask( - data=metadata, field="metadata", span=self._otel_span - ) attributes = create_trace_attributes( - name=name, - user_id=user_id, - session_id=session_id, - version=version, input=media_processed_input, output=media_processed_output, - metadata=media_processed_metadata, - tags=tags, - public=public, ) self._otel_span.set_attributes(attributes) return self + def set_trace_as_public(self) -> "LangfuseObservationWrapper": + """Make this trace publicly accessible via its URL. + + When a trace is published, anyone with the trace link can view the full trace + without needing to be logged in to Langfuse. This action cannot be undone + programmatically - once any span in a trace is published, the entire trace + becomes public. + + Returns: + The span instance for method chaining. + """ + if not self._otel_span.is_recording(): + return self + + attributes = create_trace_attributes(public=True) + + self._otel_span.set_attributes(attributes) + + return self + @overload def score( self, @@ -273,7 +283,9 @@ def score( name: str, value: float, score_id: Optional[str] = None, - data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None, + data_type: Optional[ + Literal[ScoreDataType.NUMERIC, ScoreDataType.BOOLEAN] + ] = None, comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, @@ -287,7 +299,9 @@ def score( name: str, value: str, score_id: Optional[str] = None, - data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", + data_type: Optional[ + Literal[ScoreDataType.CATEGORICAL] + ] = ScoreDataType.CATEGORICAL, comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, @@ -323,7 +337,7 @@ def score( Example: ```python - with langfuse.start_as_current_span(name="process-query") as span: + with langfuse.start_as_current_observation(name="process-query") as span: # Do work result = process_data() @@ -356,7 +370,9 @@ def score_trace( name: str, value: float, score_id: Optional[str] = None, - data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None, + data_type: Optional[ + Literal[ScoreDataType.NUMERIC, ScoreDataType.BOOLEAN] + ] = None, comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, @@ -370,7 +386,9 @@ def score_trace( name: str, value: str, score_id: Optional[str] = None, - data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", + data_type: Optional[ + Literal[ScoreDataType.CATEGORICAL] + ] = ScoreDataType.CATEGORICAL, comment: Optional[str] = None, config_id: Optional[str] = None, timestamp: Optional[datetime] = None, @@ -407,7 +425,7 @@ def score_trace( Example: ```python - with langfuse.start_as_current_span(name="handle-request") as span: + with langfuse.start_as_current_observation(name="handle-request") as span: # Process the complete request result = process_request() @@ -843,7 +861,7 @@ def start_observation( self, *, name: str, - as_type: ObservationTypeLiteral, + as_type: ObservationTypeLiteral = "span", input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -1091,7 +1109,7 @@ def start_as_current_observation( # type: ignore[misc] self, *, name: str, - as_type: ObservationTypeLiteralNoEvent, + as_type: ObservationTypeLiteralNoEvent = "span", input: Optional[Any] = None, output: Optional[Any] = None, metadata: Optional[Any] = None, @@ -1161,375 +1179,6 @@ def start_as_current_observation( # type: ignore[misc] prompt=prompt, ) - -class LangfuseSpan(LangfuseObservationWrapper): - """Standard span implementation for general operations in Langfuse. - - This class represents a general-purpose span that can be used to trace - any operation in your application. It extends the base LangfuseObservationWrapper - with specific methods for creating child spans, generations, and updating - span-specific attributes. If possible, use a more specific type for - better observability and insights. - """ - - def __init__( - self, - *, - otel_span: otel_trace_api.Span, - langfuse_client: "Langfuse", - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - environment: Optional[str] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - ): - """Initialize a new LangfuseSpan. - - Args: - otel_span: The OpenTelemetry span to wrap - langfuse_client: Reference to the parent Langfuse client - input: Input data for the span (any JSON-serializable object) - output: Output data from the span (any JSON-serializable object) - metadata: Additional metadata to associate with the span - environment: The tracing environment - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - """ - super().__init__( - otel_span=otel_span, - as_type="span", - langfuse_client=langfuse_client, - input=input, - output=output, - metadata=metadata, - environment=environment, - version=version, - level=level, - status_message=status_message, - ) - - def start_span( - self, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - ) -> "LangfuseSpan": - """Create a new child span. - - This method creates a new child span with this span as the parent. - Unlike start_as_current_span(), this method does not set the new span - as the current span in the context. - - Args: - name: Name of the span (e.g., function or operation name) - input: Input data for the operation - output: Output data from the operation - metadata: Additional metadata to associate with the span - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - - Returns: - A new LangfuseSpan that must be ended with .end() when complete - - Example: - ```python - parent_span = langfuse.start_span(name="process-request") - try: - # Create a child span - child_span = parent_span.start_span(name="validate-input") - try: - # Do validation work - validation_result = validate(request_data) - child_span.update(output=validation_result) - finally: - child_span.end() - - # Continue with parent span - result = process_validated_data(validation_result) - parent_span.update(output=result) - finally: - parent_span.end() - ``` - """ - return self.start_observation( - name=name, - as_type="span", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ) - - def start_as_current_span( - self, - *, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - ) -> _AgnosticContextManager["LangfuseSpan"]: - """[DEPRECATED] Create a new child span and set it as the current span in a context manager. - - DEPRECATED: This method is deprecated and will be removed in a future version. - Use start_as_current_observation(as_type='span') instead. - - This method creates a new child span and sets it as the current span within - a context manager. It should be used with a 'with' statement to automatically - manage the span's lifecycle. - - Args: - name: Name of the span (e.g., function or operation name) - input: Input data for the operation - output: Output data from the operation - metadata: Additional metadata to associate with the span - version: Version identifier for the code or component - level: Importance level of the span (info, warning, error) - status_message: Optional status message for the span - - Returns: - A context manager that yields a new LangfuseSpan - - Example: - ```python - with langfuse.start_as_current_span(name="process-request") as parent_span: - # Parent span is active here - - # Create a child span with context management - with parent_span.start_as_current_span(name="validate-input") as child_span: - # Child span is active here - validation_result = validate(request_data) - child_span.update(output=validation_result) - - # Back to parent span context - result = process_validated_data(validation_result) - parent_span.update(output=result) - ``` - """ - warnings.warn( - "start_as_current_span is deprecated and will be removed in a future version. " - "Use start_as_current_observation(as_type='span') instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.start_as_current_observation( - name=name, - as_type="span", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - ) - - def start_generation( - self, - *, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - completion_start_time: Optional[datetime] = None, - model: Optional[str] = None, - model_parameters: Optional[Dict[str, MapValue]] = None, - usage_details: Optional[Dict[str, int]] = None, - cost_details: Optional[Dict[str, float]] = None, - prompt: Optional[PromptClient] = None, - ) -> "LangfuseGeneration": - """[DEPRECATED] Create a new child generation span. - - DEPRECATED: This method is deprecated and will be removed in a future version. - Use start_observation(as_type='generation') instead. - - This method creates a new child generation span with this span as the parent. - Generation spans are specialized for AI/LLM operations and include additional - fields for model information, usage stats, and costs. - - Unlike start_as_current_generation(), this method does not set the new span - as the current span in the context. - - Args: - name: Name of the generation operation - input: Input data for the model (e.g., prompts) - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management - - Returns: - A new LangfuseGeneration that must be ended with .end() when complete - - Example: - ```python - span = langfuse.start_span(name="process-query") - try: - # Create a generation child span - generation = span.start_generation( - name="generate-answer", - model="gpt-4", - input={"prompt": "Explain quantum computing"} - ) - try: - # Call model API - response = llm.generate(...) - - generation.update( - output=response.text, - usage_details={ - "prompt_tokens": response.usage.prompt_tokens, - "completion_tokens": response.usage.completion_tokens - } - ) - finally: - generation.end() - - # Continue with parent span - span.update(output={"answer": response.text, "source": "gpt-4"}) - finally: - span.end() - ``` - """ - warnings.warn( - "start_generation is deprecated and will be removed in a future version. " - "Use start_observation(as_type='generation') instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.start_observation( - name=name, - as_type="generation", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ) - - def start_as_current_generation( - self, - *, - name: str, - input: Optional[Any] = None, - output: Optional[Any] = None, - metadata: Optional[Any] = None, - version: Optional[str] = None, - level: Optional[SpanLevel] = None, - status_message: Optional[str] = None, - completion_start_time: Optional[datetime] = None, - model: Optional[str] = None, - model_parameters: Optional[Dict[str, MapValue]] = None, - usage_details: Optional[Dict[str, int]] = None, - cost_details: Optional[Dict[str, float]] = None, - prompt: Optional[PromptClient] = None, - ) -> _AgnosticContextManager["LangfuseGeneration"]: - """[DEPRECATED] Create a new child generation span and set it as the current span in a context manager. - - DEPRECATED: This method is deprecated and will be removed in a future version. - Use start_as_current_observation(as_type='generation') instead. - - This method creates a new child generation span and sets it as the current span - within a context manager. Generation spans are specialized for AI/LLM operations - and include additional fields for model information, usage stats, and costs. - - Args: - name: Name of the generation operation - input: Input data for the model (e.g., prompts) - output: Output from the model (e.g., completions) - metadata: Additional metadata to associate with the generation - version: Version identifier for the model or component - level: Importance level of the generation (info, warning, error) - status_message: Optional status message for the generation - completion_start_time: When the model started generating the response - model: Name/identifier of the AI model used (e.g., "gpt-4") - model_parameters: Parameters used for the model (e.g., temperature, max_tokens) - usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) - cost_details: Cost information for the model call - prompt: Associated prompt template from Langfuse prompt management - - Returns: - A context manager that yields a new LangfuseGeneration - - Example: - ```python - with langfuse.start_as_current_span(name="process-request") as span: - # Prepare data - query = preprocess_user_query(user_input) - - # Create a generation span with context management - with span.start_as_current_generation( - name="generate-answer", - model="gpt-4", - input={"query": query} - ) as generation: - # Generation span is active here - response = llm.generate(query) - - # Update with results - generation.update( - output=response.text, - usage_details={ - "prompt_tokens": response.usage.prompt_tokens, - "completion_tokens": response.usage.completion_tokens - } - ) - - # Back to parent span context - span.update(output={"answer": response.text, "source": "gpt-4"}) - ``` - """ - warnings.warn( - "start_as_current_generation is deprecated and will be removed in a future version. " - "Use start_as_current_observation(as_type='generation') instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.start_as_current_observation( - name=name, - as_type="generation", - input=input, - output=output, - metadata=metadata, - version=version, - level=level, - status_message=status_message, - completion_start_time=completion_start_time, - model=model, - model_parameters=model_parameters, - usage_details=usage_details, - cost_details=cost_details, - prompt=prompt, - ) - def create_event( self, *, @@ -1583,6 +1232,56 @@ def create_event( ) +class LangfuseSpan(LangfuseObservationWrapper): + """Standard span implementation for general operations in Langfuse. + + This class represents a general-purpose span that can be used to trace + any operation in your application. It extends the base LangfuseObservationWrapper + with specific methods for creating child spans, generations, and updating + span-specific attributes. If possible, use a more specific type for + better observability and insights. + """ + + def __init__( + self, + *, + otel_span: otel_trace_api.Span, + langfuse_client: "Langfuse", + input: Optional[Any] = None, + output: Optional[Any] = None, + metadata: Optional[Any] = None, + environment: Optional[str] = None, + version: Optional[str] = None, + level: Optional[SpanLevel] = None, + status_message: Optional[str] = None, + ): + """Initialize a new LangfuseSpan. + + Args: + otel_span: The OpenTelemetry span to wrap + langfuse_client: Reference to the parent Langfuse client + input: Input data for the span (any JSON-serializable object) + output: Output data from the span (any JSON-serializable object) + metadata: Additional metadata to associate with the span + environment: The tracing environment + version: Version identifier for the code or component + level: Importance level of the span (info, warning, error) + status_message: Optional status message for the span + """ + super().__init__( + otel_span=otel_span, + as_type="span", + langfuse_client=langfuse_client, + input=input, + output=output, + metadata=metadata, + environment=environment, + version=version, + level=level, + status_message=status_message, + ) + + class LangfuseGeneration(LangfuseObservationWrapper): """Specialized span implementation for AI model generations in Langfuse. diff --git a/langfuse/_client/span_filter.py b/langfuse/_client/span_filter.py new file mode 100644 index 000000000..0bd3fc558 --- /dev/null +++ b/langfuse/_client/span_filter.py @@ -0,0 +1,76 @@ +"""Span filter predicates for controlling OpenTelemetry span export. + +This module provides composable filter functions that determine which spans +the LangfuseSpanProcessor forwards to the Langfuse backend. +""" + +from opentelemetry.sdk.trace import ReadableSpan + +from langfuse._client.constants import LANGFUSE_TRACER_NAME + +KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = frozenset( + { + LANGFUSE_TRACER_NAME, + "agent_framework", + "ai", + "haystack", + "langsmith", + "litellm", + "openinference", + "opentelemetry.instrumentation.anthropic", + "strands-agents", + "vllm", + } +) +"""Known instrumentation scope namespace prefixes. + +Prefix matching is boundary-aware: +- exact match (``scope == prefix``) +- direct descendant scopes (``scope.startswith(prefix + ".")``) + +Please create a Github issue in https://github.com/langfuse/langfuse if you'd like to expand this default allow list. +""" + + +def is_langfuse_span(span: ReadableSpan) -> bool: + """Return whether the span was created by the Langfuse SDK tracer.""" + return ( + span.instrumentation_scope is not None + and span.instrumentation_scope.name == LANGFUSE_TRACER_NAME + ) + + +def is_genai_span(span: ReadableSpan) -> bool: + """Return whether the span has any ``gen_ai.*`` semantic convention attribute.""" + if span.attributes is None: + return False + + return any( + isinstance(key, str) and key.startswith("gen_ai") + for key in span.attributes.keys() + ) + + +def _matches_scope_prefix(scope_name: str, prefix: str) -> bool: + """Return whether a scope matches a prefix using namespace boundaries.""" + return scope_name == prefix or scope_name.startswith(f"{prefix}.") + + +def is_known_llm_instrumentor(span: ReadableSpan) -> bool: + """Return whether the span comes from a known LLM instrumentation scope.""" + if span.instrumentation_scope is None: + return False + + scope_name = span.instrumentation_scope.name + + return any( + _matches_scope_prefix(scope_name, prefix) + for prefix in KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES + ) + + +def is_default_export_span(span: ReadableSpan) -> bool: + """Return whether a span should be exported by default.""" + return ( + is_langfuse_span(span) or is_genai_span(span) or is_known_llm_instrumentor(span) + ) diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index a7d9fd2f4..3750789c0 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -13,7 +13,7 @@ import base64 import os -from typing import Dict, List, Optional +from typing import Callable, Dict, List, Optional from opentelemetry import context as context_api from opentelemetry.context import Context @@ -22,13 +22,13 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.trace import format_span_id -from langfuse._client.constants import LANGFUSE_TRACER_NAME from langfuse._client.environment_variables import ( LANGFUSE_FLUSH_AT, LANGFUSE_FLUSH_INTERVAL, LANGFUSE_OTEL_TRACES_EXPORT_PATH, ) from langfuse._client.propagation import _get_propagated_attributes_from_context +from langfuse._client.span_filter import is_default_export_span, is_langfuse_span from langfuse._client.utils import span_formatter from langfuse.logger import langfuse_logger from langfuse.version import __version__ as langfuse_version @@ -61,6 +61,7 @@ def __init__( flush_at: Optional[int] = None, flush_interval: Optional[float] = None, blocked_instrumentation_scopes: Optional[List[str]] = None, + should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, ): self.public_key = public_key @@ -69,6 +70,7 @@ def __init__( if blocked_instrumentation_scopes is not None else [] ) + self._should_export_span = should_export_span or is_default_export_span env_flush_at = os.environ.get(LANGFUSE_FLUSH_AT, None) flush_at = flush_at or int(env_flush_at) if env_flush_at is not None else None @@ -134,7 +136,7 @@ def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None def on_end(self, span: ReadableSpan) -> None: # Only export spans that belong to the scoped project # This is important to not send spans to wrong project in multi-project setups - if self._is_langfuse_span(span) and not self._is_langfuse_project_span(span): + if is_langfuse_span(span) and not self._is_langfuse_project_span(span): langfuse_logger.debug( f"Security: Span rejected - belongs to project '{span.instrumentation_scope.attributes.get('public_key') if span.instrumentation_scope and span.instrumentation_scope.attributes else None}' but processor is for '{self.public_key}'. " f"This prevents cross-project data leakage in multi-project environments." @@ -143,6 +145,30 @@ def on_end(self, span: ReadableSpan) -> None: # Do not export spans from blocked instrumentation scopes if self._is_blocked_instrumentation_scope(span): + langfuse_logger.debug( + "Trace: Dropping span due to blocked instrumentation scope | " + f"span_name='{span.name}' | " + f"instrumentation_scope='{self._get_scope_name(span)}'" + ) + return + + # Apply custom or default span filter + try: + should_export = self._should_export_span(span) + except Exception as error: + langfuse_logger.error( + "Trace: should_export_span callback raised an error. " + f"Dropping span name='{span.name}' scope='{self._get_scope_name(span)}'. " + f"Error: {error}" + ) + return + + if not should_export: + langfuse_logger.debug( + "Trace: Dropping span due to should_export_span filter | " + f"span_name='{span.name}' | " + f"instrumentation_scope='{self._get_scope_name(span)}'" + ) return langfuse_logger.debug( @@ -151,13 +177,6 @@ def on_end(self, span: ReadableSpan) -> None: super().on_end(span) - @staticmethod - def _is_langfuse_span(span: ReadableSpan) -> bool: - return ( - span.instrumentation_scope is not None - and span.instrumentation_scope.name == LANGFUSE_TRACER_NAME - ) - def _is_blocked_instrumentation_scope(self, span: ReadableSpan) -> bool: return ( span.instrumentation_scope is not None @@ -165,7 +184,7 @@ def _is_blocked_instrumentation_scope(self, span: ReadableSpan) -> bool: ) def _is_langfuse_project_span(self, span: ReadableSpan) -> bool: - if not LangfuseSpanProcessor._is_langfuse_span(span): + if not is_langfuse_span(span): return False if span.instrumentation_scope is not None: @@ -178,3 +197,10 @@ def _is_langfuse_project_span(self, span: ReadableSpan) -> bool: return public_key_on_span == self.public_key return False + + @staticmethod + def _get_scope_name(span: ReadableSpan) -> Optional[str]: + if span.instrumentation_scope is None: + return None + + return span.instrumentation_scope.name diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index de8dfb576..58b24e742 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -10,10 +10,8 @@ from langfuse._client.environment_variables import LANGFUSE_MEDIA_UPLOAD_ENABLED from langfuse._utils import _get_timestamp -from langfuse.api import GetMediaUploadUrlRequest, PatchMediaBody -from langfuse.api.client import FernLangfuse +from langfuse.api import LangfuseAPI, MediaContentType from langfuse.api.core import ApiError -from langfuse.api.resources.media.types.media_content_type import MediaContentType from langfuse.media import LangfuseMedia from .media_upload_queue import UploadMediaJob @@ -28,7 +26,7 @@ class MediaManager: def __init__( self, *, - api_client: FernLangfuse, + api_client: LangfuseAPI, httpx_client: httpx.Client, media_upload_queue: Queue, max_retries: Optional[int] = 3, @@ -221,14 +219,12 @@ def _process_upload_media_job( ) -> None: upload_url_response = self._request_with_backoff( self._api_client.media.get_upload_url, - request=GetMediaUploadUrlRequest( - contentLength=data["content_length"], - contentType=cast(MediaContentType, data["content_type"]), - sha256Hash=data["content_sha256_hash"], - field=data["field"], - traceId=data["trace_id"], - observationId=data["observation_id"], - ), + content_length=data["content_length"], + content_type=cast(MediaContentType, data["content_type"]), + sha256hash=data["content_sha256_hash"], + field=data["field"], + trace_id=data["trace_id"], + observation_id=data["observation_id"], ) upload_url = upload_url_response.upload_url @@ -268,12 +264,10 @@ def _process_upload_media_job( self._request_with_backoff( self._api_client.media.patch, media_id=data["media_id"], - request=PatchMediaBody( - uploadedAt=_get_timestamp(), - uploadHttpStatus=upload_response.status_code, - uploadHttpError=upload_response.text, - uploadTimeMs=upload_time_ms, - ), + uploaded_at=_get_timestamp(), + upload_http_status=upload_response.status_code, + upload_http_error=upload_response.text, + upload_time_ms=upload_time_ms, ) self._log.debug( diff --git a/langfuse/_task_manager/score_ingestion_consumer.py b/langfuse/_task_manager/score_ingestion_consumer.py index 1a5b61f91..f53ea08f0 100644 --- a/langfuse/_task_manager/score_ingestion_consumer.py +++ b/langfuse/_task_manager/score_ingestion_consumer.py @@ -7,23 +7,19 @@ from typing import Any, List, Optional import backoff - -from ..version import __version__ as langfuse_version - -try: - import pydantic.v1 as pydantic -except ImportError: - import pydantic # type: ignore +from pydantic import BaseModel from langfuse._utils.parse_error import handle_exception from langfuse._utils.request import APIError, LangfuseClient from langfuse._utils.serializer import EventSerializer +from ..version import __version__ as langfuse_version + MAX_EVENT_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_EVENT_SIZE_BYTES", 1_000_000)) MAX_BATCH_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_BATCH_SIZE_BYTES", 2_500_000)) -class ScoreIngestionMetadata(pydantic.BaseModel): +class ScoreIngestionMetadata(BaseModel): batch_size: int sdk_name: str sdk_version: str @@ -78,8 +74,8 @@ def _next(self) -> list: ) # convert pydantic models to dicts - if "body" in event and isinstance(event["body"], pydantic.BaseModel): - event["body"] = event["body"].dict(exclude_none=True) + if "body" in event and isinstance(event["body"], BaseModel): + event["body"] = event["body"].model_dump(exclude_none=True) item_size = self._get_item_size(event) @@ -156,7 +152,7 @@ def _upload_batch(self, batch: List[Any]) -> None: sdk_name="python", sdk_version=langfuse_version, public_key=self._public_key, - ).dict() + ).model_dump() @backoff.on_exception( backoff.expo, Exception, max_tries=self._max_retries, logger=None diff --git a/langfuse/_utils/parse_error.py b/langfuse/_utils/parse_error.py index cb5749f93..4fd8d6a69 100644 --- a/langfuse/_utils/parse_error.py +++ b/langfuse/_utils/parse_error.py @@ -3,17 +3,17 @@ # our own api errors from langfuse._utils.request import APIError, APIErrors -from langfuse.api.core import ApiError # fern api errors -from langfuse.api.resources.commons.errors import ( +from langfuse.api import ( AccessDeniedError, Error, MethodNotAllowedError, NotFoundError, + ServiceUnavailableError, UnauthorizedError, ) -from langfuse.api.resources.health.errors import ServiceUnavailableError +from langfuse.api.core import ApiError SUPPORT_URL = "https://langfuse.com/support" API_DOCS_URL = "https://api.reference.langfuse.com" diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 1350ea00c..c2dad3312 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -1,5 +1,6 @@ """@private""" +import datetime as dt import enum import math from asyncio import Queue @@ -14,7 +15,6 @@ from pydantic import BaseModel -from langfuse.api.core import pydantic_utilities, serialize_datetime from langfuse.media import LangfuseMedia # Attempt to import Serializable @@ -98,17 +98,13 @@ def default(self, obj: Any) -> Any: return obj.isoformat() if isinstance(obj, BaseModel): - obj.model_rebuild() if pydantic_utilities.IS_PYDANTIC_V2 else obj.update_forward_refs() # This method forces the OpenAI model to instantiate its serializer to avoid errors when serializing + obj.model_rebuild() # For LlamaIndex models, we need to rebuild the raw model as well if they include OpenAI models if isinstance(raw := getattr(obj, "raw", None), BaseModel): - raw.model_rebuild() if pydantic_utilities.IS_PYDANTIC_V2 else raw.update_forward_refs() + raw.model_rebuild() - return ( - obj.model_dump() - if pydantic_utilities.IS_PYDANTIC_V2 - else obj.dict() - ) + return obj.model_dump() if isinstance(obj, Path): return str(obj) @@ -188,3 +184,22 @@ def is_js_safe_integer(value: int) -> bool: min_safe_int = -(2**53) + 1 return min_safe_int <= value <= max_safe_int + + +def serialize_datetime(v: dt.datetime) -> str: + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname( + None + ): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/langfuse/api/.fern/metadata.json b/langfuse/api/.fern/metadata.json new file mode 100644 index 000000000..4f1a155c9 --- /dev/null +++ b/langfuse/api/.fern/metadata.json @@ -0,0 +1,14 @@ +{ + "cliVersion": "3.30.3", + "generatorName": "fernapi/fern-python-sdk", + "generatorVersion": "4.46.2", + "generatorConfig": { + "pydantic_config": { + "enum_type": "python_enums", + "version": "v2" + }, + "client": { + "class_name": "LangfuseAPI" + } + } +} \ No newline at end of file diff --git a/langfuse/api/README.md b/langfuse/api/README.md deleted file mode 100644 index 47de89ea3..000000000 --- a/langfuse/api/README.md +++ /dev/null @@ -1,161 +0,0 @@ -# Langfuse Python Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Langfuse%2FPython) -[![pypi](https://img.shields.io/pypi/v/langfuse)](https://pypi.python.org/pypi/langfuse) - -The Langfuse Python library provides convenient access to the Langfuse APIs from Python. - -## Table of Contents - -- [Installation](#installation) -- [Usage](#usage) -- [Async Client](#async-client) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Custom Client](#custom-client) -- [Contributing](#contributing) - -## Installation - -```sh -pip install langfuse -``` - -## Usage - -Instantiate and use the client with the following: - -```python -from langfuse import CreateAnnotationQueueRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.create_queue( - request=CreateAnnotationQueueRequest( - name="name", - score_config_ids=["scoreConfigIds", "scoreConfigIds"], - ), -) -``` - -## Async Client - -The SDK also exports an `async` client so that you can make non-blocking calls to our API. - -```python -import asyncio - -from langfuse import CreateAnnotationQueueRequest -from langfuse.client import AsyncFernLangfuse - -client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) - - -async def main() -> None: - await client.annotation_queues.create_queue( - request=CreateAnnotationQueueRequest( - name="name", - score_config_ids=["scoreConfigIds", "scoreConfigIds"], - ), - ) - - -asyncio.run(main()) -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```python -from .api_error import ApiError - -try: - client.annotation_queues.create_queue(...) -except ApiError as e: - print(e.status_code) - print(e.body) -``` - -## Advanced - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retriable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retriable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `max_retries` request option to configure this behavior. - -```python -client.annotation_queues.create_queue(...,{ - max_retries=1 -}) -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. - -```python - -from langfuse.client import FernLangfuse - -client = FernLangfuse(..., { timeout=20.0 }, ) - - -# Override timeout for a specific method -client.annotation_queues.create_queue(...,{ - timeout_in_seconds=1 -}) -``` - -### Custom Client - -You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies -and transports. -```python -import httpx -from langfuse.client import FernLangfuse - -client = FernLangfuse( - ..., - http_client=httpx.Client( - proxies="http://my.test.proxy.example.com", - transport=httpx.HTTPTransport(local_address="0.0.0.0"), - ), -) -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index d1a6414ed..eddf81af0 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -1,265 +1,586 @@ # This file was auto-generated by Fern from our API Definition. -from .resources import ( - AccessDeniedError, - AnnotationQueue, - AnnotationQueueAssignmentRequest, - AnnotationQueueItem, - AnnotationQueueObjectType, - AnnotationQueueStatus, - ApiKeyDeletionResponse, - ApiKeyList, - ApiKeyResponse, - ApiKeySummary, - AuthenticationScheme, - BaseEvent, - BasePrompt, - BaseScore, - BaseScoreV1, - BlobStorageExportFrequency, - BlobStorageExportMode, - BlobStorageIntegrationDeletionResponse, - BlobStorageIntegrationFileType, - BlobStorageIntegrationResponse, - BlobStorageIntegrationType, - BlobStorageIntegrationsResponse, - BooleanScore, - BooleanScoreV1, - BulkConfig, - CategoricalScore, - CategoricalScoreV1, - ChatMessage, - ChatMessageWithPlaceholders, - ChatMessageWithPlaceholders_Chatmessage, - ChatMessageWithPlaceholders_Placeholder, - ChatPrompt, - Comment, - CommentObjectType, - ConfigCategory, - CorrectionScore, - CreateAnnotationQueueAssignmentResponse, - CreateAnnotationQueueItemRequest, - CreateAnnotationQueueRequest, - CreateBlobStorageIntegrationRequest, - CreateChatPromptRequest, - CreateCommentRequest, - CreateCommentResponse, - CreateDatasetItemRequest, - CreateDatasetRequest, - CreateDatasetRunItemRequest, - CreateEventBody, - CreateEventEvent, - CreateGenerationBody, - CreateGenerationEvent, - CreateModelRequest, - CreateObservationEvent, - CreatePromptRequest, - CreatePromptRequest_Chat, - CreatePromptRequest_Text, - CreateScoreConfigRequest, - CreateScoreRequest, - CreateScoreResponse, - CreateScoreValue, - CreateSpanBody, - CreateSpanEvent, - CreateTextPromptRequest, - Dataset, - DatasetItem, - DatasetRun, - DatasetRunItem, - DatasetRunWithItems, - DatasetStatus, - DeleteAnnotationQueueAssignmentResponse, - DeleteAnnotationQueueItemResponse, - DeleteDatasetItemResponse, - DeleteDatasetRunResponse, - DeleteMembershipRequest, - DeleteTraceResponse, - EmptyResponse, - Error, - FilterConfig, - GetCommentsResponse, - GetMediaResponse, - GetMediaUploadUrlRequest, - GetMediaUploadUrlResponse, - GetScoresResponse, - GetScoresResponseData, - GetScoresResponseDataBoolean, - GetScoresResponseDataCategorical, - GetScoresResponseDataCorrection, - GetScoresResponseDataNumeric, - GetScoresResponseData_Boolean, - GetScoresResponseData_Categorical, - GetScoresResponseData_Correction, - GetScoresResponseData_Numeric, - GetScoresResponseTraceData, - HealthResponse, - IngestionError, - IngestionEvent, - IngestionEvent_EventCreate, - IngestionEvent_GenerationCreate, - IngestionEvent_GenerationUpdate, - IngestionEvent_ObservationCreate, - IngestionEvent_ObservationUpdate, - IngestionEvent_ScoreCreate, - IngestionEvent_SdkLog, - IngestionEvent_SpanCreate, - IngestionEvent_SpanUpdate, - IngestionEvent_TraceCreate, - IngestionResponse, - IngestionSuccess, - IngestionUsage, - LlmAdapter, - LlmConnection, - MapValue, - MediaContentType, - MembershipDeletionResponse, - MembershipRequest, - MembershipResponse, - MembershipRole, - MembershipsResponse, - MethodNotAllowedError, - MetricsResponse, - MetricsV2Response, - Model, - ModelPrice, - ModelUsageUnit, - NotFoundError, - NumericScore, - NumericScoreV1, - Observation, - ObservationBody, - ObservationLevel, - ObservationType, - Observations, - ObservationsV2Meta, - ObservationsV2Response, - ObservationsView, - ObservationsViews, - OpenAiCompletionUsageSchema, - OpenAiResponseUsageSchema, - OpenAiUsage, - OptionalObservationBody, - Organization, - OrganizationApiKey, - OrganizationApiKeysResponse, - OrganizationProject, - OrganizationProjectsResponse, - OtelAttribute, - OtelAttributeValue, - OtelResource, - OtelResourceSpan, - OtelScope, - OtelScopeSpan, - OtelSpan, - OtelTraceResponse, - PaginatedAnnotationQueueItems, - PaginatedAnnotationQueues, - PaginatedDatasetItems, - PaginatedDatasetRunItems, - PaginatedDatasetRuns, - PaginatedDatasets, - PaginatedLlmConnections, - PaginatedModels, - PaginatedSessions, - PatchMediaBody, - PlaceholderMessage, - PricingTier, - PricingTierCondition, - PricingTierInput, - PricingTierOperator, - Project, - ProjectDeletionResponse, - Projects, - Prompt, - PromptMeta, - PromptMetaListResponse, - PromptType, - Prompt_Chat, - Prompt_Text, - ResourceMeta, - ResourceType, - ResourceTypesResponse, - SchemaExtension, - SchemaResource, - SchemasResponse, - ScimEmail, - ScimFeatureSupport, - ScimName, - ScimUser, - ScimUsersListResponse, - Score, - ScoreBody, - ScoreConfig, - ScoreConfigDataType, - ScoreConfigs, - ScoreDataType, - ScoreEvent, - ScoreSource, - ScoreV1, - ScoreV1_Boolean, - ScoreV1_Categorical, - ScoreV1_Numeric, - Score_Boolean, - Score_Categorical, - Score_Correction, - Score_Numeric, - SdkLogBody, - SdkLogEvent, - ServiceProviderConfig, - ServiceUnavailableError, - Session, - SessionWithTraces, - Sort, - TextPrompt, - Trace, - TraceBody, - TraceEvent, - TraceWithDetails, - TraceWithFullDetails, - Traces, - UnauthorizedError, - UpdateAnnotationQueueItemRequest, - UpdateEventBody, - UpdateGenerationBody, - UpdateGenerationEvent, - UpdateObservationEvent, - UpdateScoreConfigRequest, - UpdateSpanBody, - UpdateSpanEvent, - UpsertLlmConnectionRequest, - Usage, - UsageDetails, - UserMeta, - annotation_queues, - blob_storage_integrations, - comments, - commons, - dataset_items, - dataset_run_items, - datasets, - health, - ingestion, - llm_connections, - media, - metrics, - metrics_v_2, - models, - observations, - observations_v_2, - opentelemetry, - organizations, - projects, - prompt_version, - prompts, - scim, - score, - score_configs, - score_v_2, - sessions, - trace, - utils, -) +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import ( + annotation_queues, + blob_storage_integrations, + comments, + commons, + dataset_items, + dataset_run_items, + datasets, + health, + ingestion, + llm_connections, + media, + metrics, + metrics_v2, + models, + observations, + observations_v2, + opentelemetry, + organizations, + projects, + prompt_version, + prompts, + scim, + score, + score_configs, + score_v2, + sessions, + trace, + utils, + ) + from .annotation_queues import ( + AnnotationQueue, + AnnotationQueueAssignmentRequest, + AnnotationQueueItem, + AnnotationQueueObjectType, + AnnotationQueueStatus, + CreateAnnotationQueueAssignmentResponse, + CreateAnnotationQueueItemRequest, + CreateAnnotationQueueRequest, + DeleteAnnotationQueueAssignmentResponse, + DeleteAnnotationQueueItemResponse, + PaginatedAnnotationQueueItems, + PaginatedAnnotationQueues, + UpdateAnnotationQueueItemRequest, + ) + from .blob_storage_integrations import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationDeletionResponse, + BlobStorageIntegrationFileType, + BlobStorageIntegrationResponse, + BlobStorageIntegrationType, + BlobStorageIntegrationsResponse, + CreateBlobStorageIntegrationRequest, + ) + from .client import AsyncLangfuseAPI, LangfuseAPI + from .comments import ( + CreateCommentRequest, + CreateCommentResponse, + GetCommentsResponse, + ) + from .commons import ( + AccessDeniedError, + BaseScore, + BaseScoreV1, + BooleanScore, + BooleanScoreV1, + CategoricalScore, + CategoricalScoreV1, + Comment, + CommentObjectType, + ConfigCategory, + CorrectionScore, + CreateScoreValue, + Dataset, + DatasetItem, + DatasetRun, + DatasetRunItem, + DatasetRunWithItems, + DatasetStatus, + Error, + MapValue, + MethodNotAllowedError, + Model, + ModelPrice, + ModelUsageUnit, + NotFoundError, + NumericScore, + NumericScoreV1, + Observation, + ObservationLevel, + ObservationsView, + PricingTier, + PricingTierCondition, + PricingTierInput, + PricingTierOperator, + Score, + ScoreConfig, + ScoreConfigDataType, + ScoreDataType, + ScoreSource, + ScoreV1, + ScoreV1_Boolean, + ScoreV1_Categorical, + ScoreV1_Numeric, + Score_Boolean, + Score_Categorical, + Score_Correction, + Score_Numeric, + Session, + SessionWithTraces, + Trace, + TraceWithDetails, + TraceWithFullDetails, + UnauthorizedError, + Usage, + ) + from .dataset_items import ( + CreateDatasetItemRequest, + DeleteDatasetItemResponse, + PaginatedDatasetItems, + ) + from .dataset_run_items import CreateDatasetRunItemRequest, PaginatedDatasetRunItems + from .datasets import ( + CreateDatasetRequest, + DeleteDatasetRunResponse, + PaginatedDatasetRuns, + PaginatedDatasets, + ) + from .health import HealthResponse, ServiceUnavailableError + from .ingestion import ( + BaseEvent, + CreateEventBody, + CreateEventEvent, + CreateGenerationBody, + CreateGenerationEvent, + CreateObservationEvent, + CreateSpanBody, + CreateSpanEvent, + IngestionError, + IngestionEvent, + IngestionEvent_EventCreate, + IngestionEvent_GenerationCreate, + IngestionEvent_GenerationUpdate, + IngestionEvent_ObservationCreate, + IngestionEvent_ObservationUpdate, + IngestionEvent_ScoreCreate, + IngestionEvent_SdkLog, + IngestionEvent_SpanCreate, + IngestionEvent_SpanUpdate, + IngestionEvent_TraceCreate, + IngestionResponse, + IngestionSuccess, + IngestionUsage, + ObservationBody, + ObservationType, + OpenAiCompletionUsageSchema, + OpenAiResponseUsageSchema, + OpenAiUsage, + OptionalObservationBody, + ScoreBody, + ScoreEvent, + SdkLogBody, + SdkLogEvent, + TraceBody, + TraceEvent, + UpdateEventBody, + UpdateGenerationBody, + UpdateGenerationEvent, + UpdateObservationEvent, + UpdateSpanBody, + UpdateSpanEvent, + UsageDetails, + ) + from .llm_connections import ( + LlmAdapter, + LlmConnection, + PaginatedLlmConnections, + UpsertLlmConnectionRequest, + ) + from .media import ( + GetMediaResponse, + GetMediaUploadUrlRequest, + GetMediaUploadUrlResponse, + MediaContentType, + PatchMediaBody, + ) + from .metrics import MetricsResponse + from .metrics_v2 import MetricsV2Response + from .models import CreateModelRequest, PaginatedModels + from .observations import Observations, ObservationsViews + from .observations_v2 import ObservationsV2Meta, ObservationsV2Response + from .opentelemetry import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + OtelTraceResponse, + ) + from .organizations import ( + DeleteMembershipRequest, + MembershipDeletionResponse, + MembershipRequest, + MembershipResponse, + MembershipRole, + MembershipsResponse, + OrganizationApiKey, + OrganizationApiKeysResponse, + OrganizationProject, + OrganizationProjectsResponse, + ) + from .projects import ( + ApiKeyDeletionResponse, + ApiKeyList, + ApiKeyResponse, + ApiKeySummary, + Organization, + Project, + ProjectDeletionResponse, + Projects, + ) + from .prompts import ( + BasePrompt, + ChatMessage, + ChatMessageType, + ChatMessageWithPlaceholders, + ChatPrompt, + CreateChatPromptRequest, + CreateChatPromptType, + CreatePromptRequest, + CreateTextPromptRequest, + CreateTextPromptType, + PlaceholderMessage, + PlaceholderMessageType, + Prompt, + PromptMeta, + PromptMetaListResponse, + PromptType, + Prompt_Chat, + Prompt_Text, + TextPrompt, + ) + from .scim import ( + AuthenticationScheme, + BulkConfig, + EmptyResponse, + FilterConfig, + ResourceMeta, + ResourceType, + ResourceTypesResponse, + SchemaExtension, + SchemaResource, + SchemasResponse, + ScimEmail, + ScimFeatureSupport, + ScimName, + ScimUser, + ScimUsersListResponse, + ServiceProviderConfig, + UserMeta, + ) + from .score import CreateScoreRequest, CreateScoreResponse + from .score_configs import ( + CreateScoreConfigRequest, + ScoreConfigs, + UpdateScoreConfigRequest, + ) + from .score_v2 import ( + GetScoresResponse, + GetScoresResponseData, + GetScoresResponseDataBoolean, + GetScoresResponseDataCategorical, + GetScoresResponseDataCorrection, + GetScoresResponseDataNumeric, + GetScoresResponseData_Boolean, + GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, + GetScoresResponseData_Numeric, + GetScoresResponseTraceData, + ) + from .sessions import PaginatedSessions + from .trace import DeleteTraceResponse, Sort, Traces +_dynamic_imports: typing.Dict[str, str] = { + "AccessDeniedError": ".commons", + "AnnotationQueue": ".annotation_queues", + "AnnotationQueueAssignmentRequest": ".annotation_queues", + "AnnotationQueueItem": ".annotation_queues", + "AnnotationQueueObjectType": ".annotation_queues", + "AnnotationQueueStatus": ".annotation_queues", + "ApiKeyDeletionResponse": ".projects", + "ApiKeyList": ".projects", + "ApiKeyResponse": ".projects", + "ApiKeySummary": ".projects", + "AsyncLangfuseAPI": ".client", + "AuthenticationScheme": ".scim", + "BaseEvent": ".ingestion", + "BasePrompt": ".prompts", + "BaseScore": ".commons", + "BaseScoreV1": ".commons", + "BlobStorageExportFrequency": ".blob_storage_integrations", + "BlobStorageExportMode": ".blob_storage_integrations", + "BlobStorageIntegrationDeletionResponse": ".blob_storage_integrations", + "BlobStorageIntegrationFileType": ".blob_storage_integrations", + "BlobStorageIntegrationResponse": ".blob_storage_integrations", + "BlobStorageIntegrationType": ".blob_storage_integrations", + "BlobStorageIntegrationsResponse": ".blob_storage_integrations", + "BooleanScore": ".commons", + "BooleanScoreV1": ".commons", + "BulkConfig": ".scim", + "CategoricalScore": ".commons", + "CategoricalScoreV1": ".commons", + "ChatMessage": ".prompts", + "ChatMessageType": ".prompts", + "ChatMessageWithPlaceholders": ".prompts", + "ChatPrompt": ".prompts", + "Comment": ".commons", + "CommentObjectType": ".commons", + "ConfigCategory": ".commons", + "CorrectionScore": ".commons", + "CreateAnnotationQueueAssignmentResponse": ".annotation_queues", + "CreateAnnotationQueueItemRequest": ".annotation_queues", + "CreateAnnotationQueueRequest": ".annotation_queues", + "CreateBlobStorageIntegrationRequest": ".blob_storage_integrations", + "CreateChatPromptRequest": ".prompts", + "CreateChatPromptType": ".prompts", + "CreateCommentRequest": ".comments", + "CreateCommentResponse": ".comments", + "CreateDatasetItemRequest": ".dataset_items", + "CreateDatasetRequest": ".datasets", + "CreateDatasetRunItemRequest": ".dataset_run_items", + "CreateEventBody": ".ingestion", + "CreateEventEvent": ".ingestion", + "CreateGenerationBody": ".ingestion", + "CreateGenerationEvent": ".ingestion", + "CreateModelRequest": ".models", + "CreateObservationEvent": ".ingestion", + "CreatePromptRequest": ".prompts", + "CreateScoreConfigRequest": ".score_configs", + "CreateScoreRequest": ".score", + "CreateScoreResponse": ".score", + "CreateScoreValue": ".commons", + "CreateSpanBody": ".ingestion", + "CreateSpanEvent": ".ingestion", + "CreateTextPromptRequest": ".prompts", + "CreateTextPromptType": ".prompts", + "Dataset": ".commons", + "DatasetItem": ".commons", + "DatasetRun": ".commons", + "DatasetRunItem": ".commons", + "DatasetRunWithItems": ".commons", + "DatasetStatus": ".commons", + "DeleteAnnotationQueueAssignmentResponse": ".annotation_queues", + "DeleteAnnotationQueueItemResponse": ".annotation_queues", + "DeleteDatasetItemResponse": ".dataset_items", + "DeleteDatasetRunResponse": ".datasets", + "DeleteMembershipRequest": ".organizations", + "DeleteTraceResponse": ".trace", + "EmptyResponse": ".scim", + "Error": ".commons", + "FilterConfig": ".scim", + "GetCommentsResponse": ".comments", + "GetMediaResponse": ".media", + "GetMediaUploadUrlRequest": ".media", + "GetMediaUploadUrlResponse": ".media", + "GetScoresResponse": ".score_v2", + "GetScoresResponseData": ".score_v2", + "GetScoresResponseDataBoolean": ".score_v2", + "GetScoresResponseDataCategorical": ".score_v2", + "GetScoresResponseDataCorrection": ".score_v2", + "GetScoresResponseDataNumeric": ".score_v2", + "GetScoresResponseData_Boolean": ".score_v2", + "GetScoresResponseData_Categorical": ".score_v2", + "GetScoresResponseData_Correction": ".score_v2", + "GetScoresResponseData_Numeric": ".score_v2", + "GetScoresResponseTraceData": ".score_v2", + "HealthResponse": ".health", + "IngestionError": ".ingestion", + "IngestionEvent": ".ingestion", + "IngestionEvent_EventCreate": ".ingestion", + "IngestionEvent_GenerationCreate": ".ingestion", + "IngestionEvent_GenerationUpdate": ".ingestion", + "IngestionEvent_ObservationCreate": ".ingestion", + "IngestionEvent_ObservationUpdate": ".ingestion", + "IngestionEvent_ScoreCreate": ".ingestion", + "IngestionEvent_SdkLog": ".ingestion", + "IngestionEvent_SpanCreate": ".ingestion", + "IngestionEvent_SpanUpdate": ".ingestion", + "IngestionEvent_TraceCreate": ".ingestion", + "IngestionResponse": ".ingestion", + "IngestionSuccess": ".ingestion", + "IngestionUsage": ".ingestion", + "LangfuseAPI": ".client", + "LlmAdapter": ".llm_connections", + "LlmConnection": ".llm_connections", + "MapValue": ".commons", + "MediaContentType": ".media", + "MembershipDeletionResponse": ".organizations", + "MembershipRequest": ".organizations", + "MembershipResponse": ".organizations", + "MembershipRole": ".organizations", + "MembershipsResponse": ".organizations", + "MethodNotAllowedError": ".commons", + "MetricsResponse": ".metrics", + "MetricsV2Response": ".metrics_v2", + "Model": ".commons", + "ModelPrice": ".commons", + "ModelUsageUnit": ".commons", + "NotFoundError": ".commons", + "NumericScore": ".commons", + "NumericScoreV1": ".commons", + "Observation": ".commons", + "ObservationBody": ".ingestion", + "ObservationLevel": ".commons", + "ObservationType": ".ingestion", + "Observations": ".observations", + "ObservationsV2Meta": ".observations_v2", + "ObservationsV2Response": ".observations_v2", + "ObservationsView": ".commons", + "ObservationsViews": ".observations", + "OpenAiCompletionUsageSchema": ".ingestion", + "OpenAiResponseUsageSchema": ".ingestion", + "OpenAiUsage": ".ingestion", + "OptionalObservationBody": ".ingestion", + "Organization": ".projects", + "OrganizationApiKey": ".organizations", + "OrganizationApiKeysResponse": ".organizations", + "OrganizationProject": ".organizations", + "OrganizationProjectsResponse": ".organizations", + "OtelAttribute": ".opentelemetry", + "OtelAttributeValue": ".opentelemetry", + "OtelResource": ".opentelemetry", + "OtelResourceSpan": ".opentelemetry", + "OtelScope": ".opentelemetry", + "OtelScopeSpan": ".opentelemetry", + "OtelSpan": ".opentelemetry", + "OtelTraceResponse": ".opentelemetry", + "PaginatedAnnotationQueueItems": ".annotation_queues", + "PaginatedAnnotationQueues": ".annotation_queues", + "PaginatedDatasetItems": ".dataset_items", + "PaginatedDatasetRunItems": ".dataset_run_items", + "PaginatedDatasetRuns": ".datasets", + "PaginatedDatasets": ".datasets", + "PaginatedLlmConnections": ".llm_connections", + "PaginatedModels": ".models", + "PaginatedSessions": ".sessions", + "PatchMediaBody": ".media", + "PlaceholderMessage": ".prompts", + "PlaceholderMessageType": ".prompts", + "PricingTier": ".commons", + "PricingTierCondition": ".commons", + "PricingTierInput": ".commons", + "PricingTierOperator": ".commons", + "Project": ".projects", + "ProjectDeletionResponse": ".projects", + "Projects": ".projects", + "Prompt": ".prompts", + "PromptMeta": ".prompts", + "PromptMetaListResponse": ".prompts", + "PromptType": ".prompts", + "Prompt_Chat": ".prompts", + "Prompt_Text": ".prompts", + "ResourceMeta": ".scim", + "ResourceType": ".scim", + "ResourceTypesResponse": ".scim", + "SchemaExtension": ".scim", + "SchemaResource": ".scim", + "SchemasResponse": ".scim", + "ScimEmail": ".scim", + "ScimFeatureSupport": ".scim", + "ScimName": ".scim", + "ScimUser": ".scim", + "ScimUsersListResponse": ".scim", + "Score": ".commons", + "ScoreBody": ".ingestion", + "ScoreConfig": ".commons", + "ScoreConfigDataType": ".commons", + "ScoreConfigs": ".score_configs", + "ScoreDataType": ".commons", + "ScoreEvent": ".ingestion", + "ScoreSource": ".commons", + "ScoreV1": ".commons", + "ScoreV1_Boolean": ".commons", + "ScoreV1_Categorical": ".commons", + "ScoreV1_Numeric": ".commons", + "Score_Boolean": ".commons", + "Score_Categorical": ".commons", + "Score_Correction": ".commons", + "Score_Numeric": ".commons", + "SdkLogBody": ".ingestion", + "SdkLogEvent": ".ingestion", + "ServiceProviderConfig": ".scim", + "ServiceUnavailableError": ".health", + "Session": ".commons", + "SessionWithTraces": ".commons", + "Sort": ".trace", + "TextPrompt": ".prompts", + "Trace": ".commons", + "TraceBody": ".ingestion", + "TraceEvent": ".ingestion", + "TraceWithDetails": ".commons", + "TraceWithFullDetails": ".commons", + "Traces": ".trace", + "UnauthorizedError": ".commons", + "UpdateAnnotationQueueItemRequest": ".annotation_queues", + "UpdateEventBody": ".ingestion", + "UpdateGenerationBody": ".ingestion", + "UpdateGenerationEvent": ".ingestion", + "UpdateObservationEvent": ".ingestion", + "UpdateScoreConfigRequest": ".score_configs", + "UpdateSpanBody": ".ingestion", + "UpdateSpanEvent": ".ingestion", + "UpsertLlmConnectionRequest": ".llm_connections", + "Usage": ".commons", + "UsageDetails": ".ingestion", + "UserMeta": ".scim", + "annotation_queues": ".annotation_queues", + "blob_storage_integrations": ".blob_storage_integrations", + "comments": ".comments", + "commons": ".commons", + "dataset_items": ".dataset_items", + "dataset_run_items": ".dataset_run_items", + "datasets": ".datasets", + "health": ".health", + "ingestion": ".ingestion", + "llm_connections": ".llm_connections", + "media": ".media", + "metrics": ".metrics", + "metrics_v2": ".metrics_v2", + "models": ".models", + "observations": ".observations", + "observations_v2": ".observations_v2", + "opentelemetry": ".opentelemetry", + "organizations": ".organizations", + "projects": ".projects", + "prompt_version": ".prompt_version", + "prompts": ".prompts", + "scim": ".scim", + "score": ".score", + "score_configs": ".score_configs", + "score_v2": ".score_v2", + "sessions": ".sessions", + "trace": ".trace", + "utils": ".utils", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + __all__ = [ "AccessDeniedError", @@ -272,6 +593,7 @@ "ApiKeyList", "ApiKeyResponse", "ApiKeySummary", + "AsyncLangfuseAPI", "AuthenticationScheme", "BaseEvent", "BasePrompt", @@ -290,9 +612,8 @@ "CategoricalScore", "CategoricalScoreV1", "ChatMessage", + "ChatMessageType", "ChatMessageWithPlaceholders", - "ChatMessageWithPlaceholders_Chatmessage", - "ChatMessageWithPlaceholders_Placeholder", "ChatPrompt", "Comment", "CommentObjectType", @@ -303,6 +624,7 @@ "CreateAnnotationQueueRequest", "CreateBlobStorageIntegrationRequest", "CreateChatPromptRequest", + "CreateChatPromptType", "CreateCommentRequest", "CreateCommentResponse", "CreateDatasetItemRequest", @@ -315,8 +637,6 @@ "CreateModelRequest", "CreateObservationEvent", "CreatePromptRequest", - "CreatePromptRequest_Chat", - "CreatePromptRequest_Text", "CreateScoreConfigRequest", "CreateScoreRequest", "CreateScoreResponse", @@ -324,6 +644,7 @@ "CreateSpanBody", "CreateSpanEvent", "CreateTextPromptRequest", + "CreateTextPromptType", "Dataset", "DatasetItem", "DatasetRun", @@ -370,6 +691,7 @@ "IngestionResponse", "IngestionSuccess", "IngestionUsage", + "LangfuseAPI", "LlmAdapter", "LlmConnection", "MapValue", @@ -425,6 +747,7 @@ "PaginatedSessions", "PatchMediaBody", "PlaceholderMessage", + "PlaceholderMessageType", "PricingTier", "PricingTierCondition", "PricingTierInput", @@ -504,10 +827,10 @@ "llm_connections", "media", "metrics", - "metrics_v_2", + "metrics_v2", "models", "observations", - "observations_v_2", + "observations_v2", "opentelemetry", "organizations", "projects", @@ -516,7 +839,7 @@ "scim", "score", "score_configs", - "score_v_2", + "score_v2", "sessions", "trace", "utils", diff --git a/langfuse/api/annotation_queues/__init__.py b/langfuse/api/annotation_queues/__init__.py new file mode 100644 index 000000000..119661e05 --- /dev/null +++ b/langfuse/api/annotation_queues/__init__.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + AnnotationQueue, + AnnotationQueueAssignmentRequest, + AnnotationQueueItem, + AnnotationQueueObjectType, + AnnotationQueueStatus, + CreateAnnotationQueueAssignmentResponse, + CreateAnnotationQueueItemRequest, + CreateAnnotationQueueRequest, + DeleteAnnotationQueueAssignmentResponse, + DeleteAnnotationQueueItemResponse, + PaginatedAnnotationQueueItems, + PaginatedAnnotationQueues, + UpdateAnnotationQueueItemRequest, + ) +_dynamic_imports: typing.Dict[str, str] = { + "AnnotationQueue": ".types", + "AnnotationQueueAssignmentRequest": ".types", + "AnnotationQueueItem": ".types", + "AnnotationQueueObjectType": ".types", + "AnnotationQueueStatus": ".types", + "CreateAnnotationQueueAssignmentResponse": ".types", + "CreateAnnotationQueueItemRequest": ".types", + "CreateAnnotationQueueRequest": ".types", + "DeleteAnnotationQueueAssignmentResponse": ".types", + "DeleteAnnotationQueueItemResponse": ".types", + "PaginatedAnnotationQueueItems": ".types", + "PaginatedAnnotationQueues": ".types", + "UpdateAnnotationQueueItemRequest": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AnnotationQueue", + "AnnotationQueueAssignmentRequest", + "AnnotationQueueItem", + "AnnotationQueueObjectType", + "AnnotationQueueStatus", + "CreateAnnotationQueueAssignmentResponse", + "CreateAnnotationQueueItemRequest", + "CreateAnnotationQueueRequest", + "DeleteAnnotationQueueAssignmentResponse", + "DeleteAnnotationQueueItemResponse", + "PaginatedAnnotationQueueItems", + "PaginatedAnnotationQueues", + "UpdateAnnotationQueueItemRequest", +] diff --git a/langfuse/api/annotation_queues/client.py b/langfuse/api/annotation_queues/client.py new file mode 100644 index 000000000..6fb3d5682 --- /dev/null +++ b/langfuse/api/annotation_queues/client.py @@ -0,0 +1,1111 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawAnnotationQueuesClient, RawAnnotationQueuesClient +from .types.annotation_queue import AnnotationQueue +from .types.annotation_queue_item import AnnotationQueueItem +from .types.annotation_queue_object_type import AnnotationQueueObjectType +from .types.annotation_queue_status import AnnotationQueueStatus +from .types.create_annotation_queue_assignment_response import ( + CreateAnnotationQueueAssignmentResponse, +) +from .types.delete_annotation_queue_assignment_response import ( + DeleteAnnotationQueueAssignmentResponse, +) +from .types.delete_annotation_queue_item_response import ( + DeleteAnnotationQueueItemResponse, +) +from .types.paginated_annotation_queue_items import PaginatedAnnotationQueueItems +from .types.paginated_annotation_queues import PaginatedAnnotationQueues + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class AnnotationQueuesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawAnnotationQueuesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawAnnotationQueuesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawAnnotationQueuesClient + """ + return self._raw_client + + def list_queues( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedAnnotationQueues: + """ + Get all annotation queues + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedAnnotationQueues + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.list_queues() + """ + _response = self._raw_client.list_queues( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def create_queue( + self, + *, + name: str, + score_config_ids: typing.Sequence[str], + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueue: + """ + Create an annotation queue + + Parameters + ---------- + name : str + + score_config_ids : typing.Sequence[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueue + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.create_queue( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], + ) + """ + _response = self._raw_client.create_queue( + name=name, + score_config_ids=score_config_ids, + description=description, + request_options=request_options, + ) + return _response.data + + def get_queue( + self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AnnotationQueue: + """ + Get an annotation queue by ID + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueue + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.get_queue( + queue_id="queueId", + ) + """ + _response = self._raw_client.get_queue( + queue_id, request_options=request_options + ) + return _response.data + + def list_queue_items( + self, + queue_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedAnnotationQueueItems: + """ + Get items for a specific annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + status : typing.Optional[AnnotationQueueStatus] + Filter by status + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedAnnotationQueueItems + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.list_queue_items( + queue_id="queueId", + ) + """ + _response = self._raw_client.list_queue_items( + queue_id, + status=status, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + def get_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueueItem: + """ + Get a specific item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueueItem + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.get_queue_item( + queue_id="queueId", + item_id="itemId", + ) + """ + _response = self._raw_client.get_queue_item( + queue_id, item_id, request_options=request_options + ) + return _response.data + + def create_queue_item( + self, + queue_id: str, + *, + object_id: str, + object_type: AnnotationQueueObjectType, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueueItem: + """ + Add an item to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + object_id : str + + object_type : AnnotationQueueObjectType + + status : typing.Optional[AnnotationQueueStatus] + Defaults to PENDING for new queue items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueueItem + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.annotation_queues import AnnotationQueueObjectType + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.create_queue_item( + queue_id="queueId", + object_id="objectId", + object_type=AnnotationQueueObjectType.TRACE, + ) + """ + _response = self._raw_client.create_queue_item( + queue_id, + object_id=object_id, + object_type=object_type, + status=status, + request_options=request_options, + ) + return _response.data + + def update_queue_item( + self, + queue_id: str, + item_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueueItem: + """ + Update an annotation queue item + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + status : typing.Optional[AnnotationQueueStatus] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueueItem + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.update_queue_item( + queue_id="queueId", + item_id="itemId", + ) + """ + _response = self._raw_client.update_queue_item( + queue_id, item_id, status=status, request_options=request_options + ) + return _response.data + + def delete_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteAnnotationQueueItemResponse: + """ + Remove an item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteAnnotationQueueItemResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.delete_queue_item( + queue_id="queueId", + item_id="itemId", + ) + """ + _response = self._raw_client.delete_queue_item( + queue_id, item_id, request_options=request_options + ) + return _response.data + + def create_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateAnnotationQueueAssignmentResponse: + """ + Create an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateAnnotationQueueAssignmentResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.create_queue_assignment( + queue_id="queueId", + user_id="userId", + ) + """ + _response = self._raw_client.create_queue_assignment( + queue_id, user_id=user_id, request_options=request_options + ) + return _response.data + + def delete_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteAnnotationQueueAssignmentResponse: + """ + Delete an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteAnnotationQueueAssignmentResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.annotation_queues.delete_queue_assignment( + queue_id="queueId", + user_id="userId", + ) + """ + _response = self._raw_client.delete_queue_assignment( + queue_id, user_id=user_id, request_options=request_options + ) + return _response.data + + +class AsyncAnnotationQueuesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawAnnotationQueuesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawAnnotationQueuesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawAnnotationQueuesClient + """ + return self._raw_client + + async def list_queues( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedAnnotationQueues: + """ + Get all annotation queues + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedAnnotationQueues + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.list_queues() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_queues( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def create_queue( + self, + *, + name: str, + score_config_ids: typing.Sequence[str], + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueue: + """ + Create an annotation queue + + Parameters + ---------- + name : str + + score_config_ids : typing.Sequence[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueue + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.create_queue( + name="name", + score_config_ids=["scoreConfigIds", "scoreConfigIds"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_queue( + name=name, + score_config_ids=score_config_ids, + description=description, + request_options=request_options, + ) + return _response.data + + async def get_queue( + self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AnnotationQueue: + """ + Get an annotation queue by ID + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueue + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.get_queue( + queue_id="queueId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_queue( + queue_id, request_options=request_options + ) + return _response.data + + async def list_queue_items( + self, + queue_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedAnnotationQueueItems: + """ + Get items for a specific annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + status : typing.Optional[AnnotationQueueStatus] + Filter by status + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedAnnotationQueueItems + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.list_queue_items( + queue_id="queueId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_queue_items( + queue_id, + status=status, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + async def get_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueueItem: + """ + Get a specific item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueueItem + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.get_queue_item( + queue_id="queueId", + item_id="itemId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_queue_item( + queue_id, item_id, request_options=request_options + ) + return _response.data + + async def create_queue_item( + self, + queue_id: str, + *, + object_id: str, + object_type: AnnotationQueueObjectType, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueueItem: + """ + Add an item to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + object_id : str + + object_type : AnnotationQueueObjectType + + status : typing.Optional[AnnotationQueueStatus] + Defaults to PENDING for new queue items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueueItem + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.annotation_queues import AnnotationQueueObjectType + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.create_queue_item( + queue_id="queueId", + object_id="objectId", + object_type=AnnotationQueueObjectType.TRACE, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_queue_item( + queue_id, + object_id=object_id, + object_type=object_type, + status=status, + request_options=request_options, + ) + return _response.data + + async def update_queue_item( + self, + queue_id: str, + item_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AnnotationQueueItem: + """ + Update an annotation queue item + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + status : typing.Optional[AnnotationQueueStatus] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AnnotationQueueItem + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.update_queue_item( + queue_id="queueId", + item_id="itemId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update_queue_item( + queue_id, item_id, status=status, request_options=request_options + ) + return _response.data + + async def delete_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteAnnotationQueueItemResponse: + """ + Remove an item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteAnnotationQueueItemResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.delete_queue_item( + queue_id="queueId", + item_id="itemId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_queue_item( + queue_id, item_id, request_options=request_options + ) + return _response.data + + async def create_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateAnnotationQueueAssignmentResponse: + """ + Create an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateAnnotationQueueAssignmentResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.create_queue_assignment( + queue_id="queueId", + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_queue_assignment( + queue_id, user_id=user_id, request_options=request_options + ) + return _response.data + + async def delete_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteAnnotationQueueAssignmentResponse: + """ + Delete an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteAnnotationQueueAssignmentResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.annotation_queues.delete_queue_assignment( + queue_id="queueId", + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_queue_assignment( + queue_id, user_id=user_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/annotation_queues/raw_client.py b/langfuse/api/annotation_queues/raw_client.py new file mode 100644 index 000000000..451095061 --- /dev/null +++ b/langfuse/api/annotation_queues/raw_client.py @@ -0,0 +1,2288 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.annotation_queue import AnnotationQueue +from .types.annotation_queue_item import AnnotationQueueItem +from .types.annotation_queue_object_type import AnnotationQueueObjectType +from .types.annotation_queue_status import AnnotationQueueStatus +from .types.create_annotation_queue_assignment_response import ( + CreateAnnotationQueueAssignmentResponse, +) +from .types.delete_annotation_queue_assignment_response import ( + DeleteAnnotationQueueAssignmentResponse, +) +from .types.delete_annotation_queue_item_response import ( + DeleteAnnotationQueueItemResponse, +) +from .types.paginated_annotation_queue_items import PaginatedAnnotationQueueItems +from .types.paginated_annotation_queues import PaginatedAnnotationQueues + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawAnnotationQueuesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list_queues( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedAnnotationQueues]: + """ + Get all annotation queues + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedAnnotationQueues] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/annotation-queues", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedAnnotationQueues, + parse_obj_as( + type_=PaginatedAnnotationQueues, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create_queue( + self, + *, + name: str, + score_config_ids: typing.Sequence[str], + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[AnnotationQueue]: + """ + Create an annotation queue + + Parameters + ---------- + name : str + + score_config_ids : typing.Sequence[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AnnotationQueue] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/annotation-queues", + method="POST", + json={ + "name": name, + "description": description, + "scoreConfigIds": score_config_ids, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueue, + parse_obj_as( + type_=AnnotationQueue, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_queue( + self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[AnnotationQueue]: + """ + Get an annotation queue by ID + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AnnotationQueue] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueue, + parse_obj_as( + type_=AnnotationQueue, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list_queue_items( + self, + queue_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedAnnotationQueueItems]: + """ + Get items for a specific annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + status : typing.Optional[AnnotationQueueStatus] + Filter by status + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedAnnotationQueueItems] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", + method="GET", + params={ + "status": status, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedAnnotationQueueItems, + parse_obj_as( + type_=PaginatedAnnotationQueueItems, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[AnnotationQueueItem]: + """ + Get a specific item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AnnotationQueueItem] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueueItem, + parse_obj_as( + type_=AnnotationQueueItem, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create_queue_item( + self, + queue_id: str, + *, + object_id: str, + object_type: AnnotationQueueObjectType, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[AnnotationQueueItem]: + """ + Add an item to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + object_id : str + + object_type : AnnotationQueueObjectType + + status : typing.Optional[AnnotationQueueStatus] + Defaults to PENDING for new queue items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AnnotationQueueItem] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", + method="POST", + json={ + "objectId": object_id, + "objectType": object_type, + "status": status, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueueItem, + parse_obj_as( + type_=AnnotationQueueItem, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def update_queue_item( + self, + queue_id: str, + item_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[AnnotationQueueItem]: + """ + Update an annotation queue item + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + status : typing.Optional[AnnotationQueueStatus] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[AnnotationQueueItem] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", + method="PATCH", + json={ + "status": status, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueueItem, + parse_obj_as( + type_=AnnotationQueueItem, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DeleteAnnotationQueueItemResponse]: + """ + Remove an item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteAnnotationQueueItemResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteAnnotationQueueItemResponse, + parse_obj_as( + type_=DeleteAnnotationQueueItemResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateAnnotationQueueAssignmentResponse]: + """ + Create an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateAnnotationQueueAssignmentResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="POST", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateAnnotationQueueAssignmentResponse, + parse_obj_as( + type_=CreateAnnotationQueueAssignmentResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DeleteAnnotationQueueAssignmentResponse]: + """ + Delete an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteAnnotationQueueAssignmentResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="DELETE", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteAnnotationQueueAssignmentResponse, + parse_obj_as( + type_=DeleteAnnotationQueueAssignmentResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawAnnotationQueuesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list_queues( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedAnnotationQueues]: + """ + Get all annotation queues + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedAnnotationQueues] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/annotation-queues", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedAnnotationQueues, + parse_obj_as( + type_=PaginatedAnnotationQueues, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create_queue( + self, + *, + name: str, + score_config_ids: typing.Sequence[str], + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[AnnotationQueue]: + """ + Create an annotation queue + + Parameters + ---------- + name : str + + score_config_ids : typing.Sequence[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AnnotationQueue] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/annotation-queues", + method="POST", + json={ + "name": name, + "description": description, + "scoreConfigIds": score_config_ids, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueue, + parse_obj_as( + type_=AnnotationQueue, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_queue( + self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[AnnotationQueue]: + """ + Get an annotation queue by ID + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AnnotationQueue] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueue, + parse_obj_as( + type_=AnnotationQueue, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list_queue_items( + self, + queue_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedAnnotationQueueItems]: + """ + Get items for a specific annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + status : typing.Optional[AnnotationQueueStatus] + Filter by status + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedAnnotationQueueItems] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", + method="GET", + params={ + "status": status, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedAnnotationQueueItems, + parse_obj_as( + type_=PaginatedAnnotationQueueItems, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[AnnotationQueueItem]: + """ + Get a specific item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AnnotationQueueItem] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueueItem, + parse_obj_as( + type_=AnnotationQueueItem, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create_queue_item( + self, + queue_id: str, + *, + object_id: str, + object_type: AnnotationQueueObjectType, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[AnnotationQueueItem]: + """ + Add an item to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + object_id : str + + object_type : AnnotationQueueObjectType + + status : typing.Optional[AnnotationQueueStatus] + Defaults to PENDING for new queue items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AnnotationQueueItem] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", + method="POST", + json={ + "objectId": object_id, + "objectType": object_type, + "status": status, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueueItem, + parse_obj_as( + type_=AnnotationQueueItem, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def update_queue_item( + self, + queue_id: str, + item_id: str, + *, + status: typing.Optional[AnnotationQueueStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[AnnotationQueueItem]: + """ + Update an annotation queue item + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + status : typing.Optional[AnnotationQueueStatus] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[AnnotationQueueItem] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", + method="PATCH", + json={ + "status": status, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + AnnotationQueueItem, + parse_obj_as( + type_=AnnotationQueueItem, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_queue_item( + self, + queue_id: str, + item_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DeleteAnnotationQueueItemResponse]: + """ + Remove an item from an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + item_id : str + The unique identifier of the annotation queue item + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteAnnotationQueueItemResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteAnnotationQueueItemResponse, + parse_obj_as( + type_=DeleteAnnotationQueueItemResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateAnnotationQueueAssignmentResponse]: + """ + Create an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateAnnotationQueueAssignmentResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="POST", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateAnnotationQueueAssignmentResponse, + parse_obj_as( + type_=CreateAnnotationQueueAssignmentResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_queue_assignment( + self, + queue_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DeleteAnnotationQueueAssignmentResponse]: + """ + Delete an assignment for a user to an annotation queue + + Parameters + ---------- + queue_id : str + The unique identifier of the annotation queue + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteAnnotationQueueAssignmentResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", + method="DELETE", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteAnnotationQueueAssignmentResponse, + parse_obj_as( + type_=DeleteAnnotationQueueAssignmentResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/annotation_queues/types/__init__.py b/langfuse/api/annotation_queues/types/__init__.py new file mode 100644 index 000000000..0d34bb763 --- /dev/null +++ b/langfuse/api/annotation_queues/types/__init__.py @@ -0,0 +1,84 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .annotation_queue import AnnotationQueue + from .annotation_queue_assignment_request import AnnotationQueueAssignmentRequest + from .annotation_queue_item import AnnotationQueueItem + from .annotation_queue_object_type import AnnotationQueueObjectType + from .annotation_queue_status import AnnotationQueueStatus + from .create_annotation_queue_assignment_response import ( + CreateAnnotationQueueAssignmentResponse, + ) + from .create_annotation_queue_item_request import CreateAnnotationQueueItemRequest + from .create_annotation_queue_request import CreateAnnotationQueueRequest + from .delete_annotation_queue_assignment_response import ( + DeleteAnnotationQueueAssignmentResponse, + ) + from .delete_annotation_queue_item_response import DeleteAnnotationQueueItemResponse + from .paginated_annotation_queue_items import PaginatedAnnotationQueueItems + from .paginated_annotation_queues import PaginatedAnnotationQueues + from .update_annotation_queue_item_request import UpdateAnnotationQueueItemRequest +_dynamic_imports: typing.Dict[str, str] = { + "AnnotationQueue": ".annotation_queue", + "AnnotationQueueAssignmentRequest": ".annotation_queue_assignment_request", + "AnnotationQueueItem": ".annotation_queue_item", + "AnnotationQueueObjectType": ".annotation_queue_object_type", + "AnnotationQueueStatus": ".annotation_queue_status", + "CreateAnnotationQueueAssignmentResponse": ".create_annotation_queue_assignment_response", + "CreateAnnotationQueueItemRequest": ".create_annotation_queue_item_request", + "CreateAnnotationQueueRequest": ".create_annotation_queue_request", + "DeleteAnnotationQueueAssignmentResponse": ".delete_annotation_queue_assignment_response", + "DeleteAnnotationQueueItemResponse": ".delete_annotation_queue_item_response", + "PaginatedAnnotationQueueItems": ".paginated_annotation_queue_items", + "PaginatedAnnotationQueues": ".paginated_annotation_queues", + "UpdateAnnotationQueueItemRequest": ".update_annotation_queue_item_request", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AnnotationQueue", + "AnnotationQueueAssignmentRequest", + "AnnotationQueueItem", + "AnnotationQueueObjectType", + "AnnotationQueueStatus", + "CreateAnnotationQueueAssignmentResponse", + "CreateAnnotationQueueItemRequest", + "CreateAnnotationQueueRequest", + "DeleteAnnotationQueueAssignmentResponse", + "DeleteAnnotationQueueItemResponse", + "PaginatedAnnotationQueueItems", + "PaginatedAnnotationQueues", + "UpdateAnnotationQueueItemRequest", +] diff --git a/langfuse/api/annotation_queues/types/annotation_queue.py b/langfuse/api/annotation_queues/types/annotation_queue.py new file mode 100644 index 000000000..89cc5d407 --- /dev/null +++ b/langfuse/api/annotation_queues/types/annotation_queue.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class AnnotationQueue(UniversalBaseModel): + id: str + name: str + description: typing.Optional[str] = None + score_config_ids: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="scoreConfigIds") + ] + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/annotation_queue_assignment_request.py b/langfuse/api/annotation_queues/types/annotation_queue_assignment_request.py new file mode 100644 index 000000000..e25e4a327 --- /dev/null +++ b/langfuse/api/annotation_queues/types/annotation_queue_assignment_request.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class AnnotationQueueAssignmentRequest(UniversalBaseModel): + user_id: typing_extensions.Annotated[str, FieldMetadata(alias="userId")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/annotation_queue_item.py b/langfuse/api/annotation_queues/types/annotation_queue_item.py new file mode 100644 index 000000000..9c4b622d8 --- /dev/null +++ b/langfuse/api/annotation_queues/types/annotation_queue_item.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .annotation_queue_object_type import AnnotationQueueObjectType +from .annotation_queue_status import AnnotationQueueStatus + + +class AnnotationQueueItem(UniversalBaseModel): + id: str + queue_id: typing_extensions.Annotated[str, FieldMetadata(alias="queueId")] + object_id: typing_extensions.Annotated[str, FieldMetadata(alias="objectId")] + object_type: typing_extensions.Annotated[ + AnnotationQueueObjectType, FieldMetadata(alias="objectType") + ] + status: AnnotationQueueStatus + completed_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completedAt") + ] = None + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py b/langfuse/api/annotation_queues/types/annotation_queue_object_type.py similarity index 89% rename from langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py rename to langfuse/api/annotation_queues/types/annotation_queue_object_type.py index 6e63a7015..af8b95e89 100644 --- a/langfuse/api/resources/annotation_queues/types/annotation_queue_object_type.py +++ b/langfuse/api/annotation_queues/types/annotation_queue_object_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class AnnotationQueueObjectType(str, enum.Enum): +class AnnotationQueueObjectType(enum.StrEnum): TRACE = "TRACE" OBSERVATION = "OBSERVATION" SESSION = "SESSION" diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue_status.py b/langfuse/api/annotation_queues/types/annotation_queue_status.py similarity index 87% rename from langfuse/api/resources/annotation_queues/types/annotation_queue_status.py rename to langfuse/api/annotation_queues/types/annotation_queue_status.py index cf075f38a..a11fe3ea8 100644 --- a/langfuse/api/resources/annotation_queues/types/annotation_queue_status.py +++ b/langfuse/api/annotation_queues/types/annotation_queue_status.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class AnnotationQueueStatus(str, enum.Enum): +class AnnotationQueueStatus(enum.StrEnum): PENDING = "PENDING" COMPLETED = "COMPLETED" diff --git a/langfuse/api/annotation_queues/types/create_annotation_queue_assignment_response.py b/langfuse/api/annotation_queues/types/create_annotation_queue_assignment_response.py new file mode 100644 index 000000000..8f040d3e4 --- /dev/null +++ b/langfuse/api/annotation_queues/types/create_annotation_queue_assignment_response.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateAnnotationQueueAssignmentResponse(UniversalBaseModel): + user_id: typing_extensions.Annotated[str, FieldMetadata(alias="userId")] + queue_id: typing_extensions.Annotated[str, FieldMetadata(alias="queueId")] + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/create_annotation_queue_item_request.py b/langfuse/api/annotation_queues/types/create_annotation_queue_item_request.py new file mode 100644 index 000000000..b81287ce5 --- /dev/null +++ b/langfuse/api/annotation_queues/types/create_annotation_queue_item_request.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .annotation_queue_object_type import AnnotationQueueObjectType +from .annotation_queue_status import AnnotationQueueStatus + + +class CreateAnnotationQueueItemRequest(UniversalBaseModel): + object_id: typing_extensions.Annotated[str, FieldMetadata(alias="objectId")] + object_type: typing_extensions.Annotated[ + AnnotationQueueObjectType, FieldMetadata(alias="objectType") + ] + status: typing.Optional[AnnotationQueueStatus] = pydantic.Field(default=None) + """ + Defaults to PENDING for new queue items + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/create_annotation_queue_request.py b/langfuse/api/annotation_queues/types/create_annotation_queue_request.py new file mode 100644 index 000000000..1415ad1a7 --- /dev/null +++ b/langfuse/api/annotation_queues/types/create_annotation_queue_request.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateAnnotationQueueRequest(UniversalBaseModel): + name: str + description: typing.Optional[str] = None + score_config_ids: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="scoreConfigIds") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/delete_annotation_queue_assignment_response.py b/langfuse/api/annotation_queues/types/delete_annotation_queue_assignment_response.py new file mode 100644 index 000000000..547e2847e --- /dev/null +++ b/langfuse/api/annotation_queues/types/delete_annotation_queue_assignment_response.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class DeleteAnnotationQueueAssignmentResponse(UniversalBaseModel): + success: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/delete_annotation_queue_item_response.py b/langfuse/api/annotation_queues/types/delete_annotation_queue_item_response.py new file mode 100644 index 000000000..ededb3a49 --- /dev/null +++ b/langfuse/api/annotation_queues/types/delete_annotation_queue_item_response.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class DeleteAnnotationQueueItemResponse(UniversalBaseModel): + success: bool + message: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/paginated_annotation_queue_items.py b/langfuse/api/annotation_queues/types/paginated_annotation_queue_items.py new file mode 100644 index 000000000..140d2efb9 --- /dev/null +++ b/langfuse/api/annotation_queues/types/paginated_annotation_queue_items.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse +from .annotation_queue_item import AnnotationQueueItem + + +class PaginatedAnnotationQueueItems(UniversalBaseModel): + data: typing.List[AnnotationQueueItem] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/paginated_annotation_queues.py b/langfuse/api/annotation_queues/types/paginated_annotation_queues.py new file mode 100644 index 000000000..e07a71ac7 --- /dev/null +++ b/langfuse/api/annotation_queues/types/paginated_annotation_queues.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse +from .annotation_queue import AnnotationQueue + + +class PaginatedAnnotationQueues(UniversalBaseModel): + data: typing.List[AnnotationQueue] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/annotation_queues/types/update_annotation_queue_item_request.py b/langfuse/api/annotation_queues/types/update_annotation_queue_item_request.py new file mode 100644 index 000000000..8f2c2f898 --- /dev/null +++ b/langfuse/api/annotation_queues/types/update_annotation_queue_item_request.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .annotation_queue_status import AnnotationQueueStatus + + +class UpdateAnnotationQueueItemRequest(UniversalBaseModel): + status: typing.Optional[AnnotationQueueStatus] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/blob_storage_integrations/__init__.py b/langfuse/api/blob_storage_integrations/__init__.py new file mode 100644 index 000000000..abd0d9e84 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/__init__.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationDeletionResponse, + BlobStorageIntegrationFileType, + BlobStorageIntegrationResponse, + BlobStorageIntegrationType, + BlobStorageIntegrationsResponse, + CreateBlobStorageIntegrationRequest, + ) +_dynamic_imports: typing.Dict[str, str] = { + "BlobStorageExportFrequency": ".types", + "BlobStorageExportMode": ".types", + "BlobStorageIntegrationDeletionResponse": ".types", + "BlobStorageIntegrationFileType": ".types", + "BlobStorageIntegrationResponse": ".types", + "BlobStorageIntegrationType": ".types", + "BlobStorageIntegrationsResponse": ".types", + "CreateBlobStorageIntegrationRequest": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BlobStorageExportFrequency", + "BlobStorageExportMode", + "BlobStorageIntegrationDeletionResponse", + "BlobStorageIntegrationFileType", + "BlobStorageIntegrationResponse", + "BlobStorageIntegrationType", + "BlobStorageIntegrationsResponse", + "CreateBlobStorageIntegrationRequest", +] diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py new file mode 100644 index 000000000..6b1f6a677 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/client.py @@ -0,0 +1,463 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import ( + AsyncRawBlobStorageIntegrationsClient, + RawBlobStorageIntegrationsClient, +) +from .types.blob_storage_export_frequency import BlobStorageExportFrequency +from .types.blob_storage_export_mode import BlobStorageExportMode +from .types.blob_storage_integration_deletion_response import ( + BlobStorageIntegrationDeletionResponse, +) +from .types.blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integration_type import BlobStorageIntegrationType +from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BlobStorageIntegrationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBlobStorageIntegrationsClient( + client_wrapper=client_wrapper + ) + + @property + def with_raw_response(self) -> RawBlobStorageIntegrationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBlobStorageIntegrationsClient + """ + return self._raw_client + + def get_blob_storage_integrations( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationsResponse: + """ + Get all blob storage integrations for the organization (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.get_blob_storage_integrations() + """ + _response = self._raw_client.get_blob_storage_integrations( + request_options=request_options + ) + return _response.data + + def upsert_blob_storage_integration( + self, + *, + project_id: str, + type: BlobStorageIntegrationType, + bucket_name: str, + region: str, + export_frequency: BlobStorageExportFrequency, + enabled: bool, + force_path_style: bool, + file_type: BlobStorageIntegrationFileType, + export_mode: BlobStorageExportMode, + endpoint: typing.Optional[str] = OMIT, + access_key_id: typing.Optional[str] = OMIT, + secret_access_key: typing.Optional[str] = OMIT, + prefix: typing.Optional[str] = OMIT, + export_start_date: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> BlobStorageIntegrationResponse: + """ + Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. + + Parameters + ---------- + project_id : str + ID of the project in which to configure the blob storage integration + + type : BlobStorageIntegrationType + + bucket_name : str + Name of the storage bucket + + region : str + Storage region + + export_frequency : BlobStorageExportFrequency + + enabled : bool + Whether the integration is active + + force_path_style : bool + Use path-style URLs for S3 requests + + file_type : BlobStorageIntegrationFileType + + export_mode : BlobStorageExportMode + + endpoint : typing.Optional[str] + Custom endpoint URL (required for S3_COMPATIBLE type) + + access_key_id : typing.Optional[str] + Access key ID for authentication + + secret_access_key : typing.Optional[str] + Secret access key for authentication (will be encrypted when stored) + + prefix : typing.Optional[str] + Path prefix for exported files (must end with forward slash if provided) + + export_start_date : typing.Optional[dt.datetime] + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationResponse + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.blob_storage_integrations import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationFileType, + BlobStorageIntegrationType, + ) + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.upsert_blob_storage_integration( + project_id="projectId", + type=BlobStorageIntegrationType.S3, + bucket_name="bucketName", + region="region", + export_frequency=BlobStorageExportFrequency.HOURLY, + enabled=True, + force_path_style=True, + file_type=BlobStorageIntegrationFileType.JSON, + export_mode=BlobStorageExportMode.FULL_HISTORY, + ) + """ + _response = self._raw_client.upsert_blob_storage_integration( + project_id=project_id, + type=type, + bucket_name=bucket_name, + region=region, + export_frequency=export_frequency, + enabled=enabled, + force_path_style=force_path_style, + file_type=file_type, + export_mode=export_mode, + endpoint=endpoint, + access_key_id=access_key_id, + secret_access_key=secret_access_key, + prefix=prefix, + export_start_date=export_start_date, + request_options=request_options, + ) + return _response.data + + def delete_blob_storage_integration( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationDeletionResponse: + """ + Delete a blob storage integration by ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationDeletionResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.delete_blob_storage_integration( + id="id", + ) + """ + _response = self._raw_client.delete_blob_storage_integration( + id, request_options=request_options + ) + return _response.data + + +class AsyncBlobStorageIntegrationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBlobStorageIntegrationsClient( + client_wrapper=client_wrapper + ) + + @property + def with_raw_response(self) -> AsyncRawBlobStorageIntegrationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBlobStorageIntegrationsClient + """ + return self._raw_client + + async def get_blob_storage_integrations( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationsResponse: + """ + Get all blob storage integrations for the organization (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.get_blob_storage_integrations() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_blob_storage_integrations( + request_options=request_options + ) + return _response.data + + async def upsert_blob_storage_integration( + self, + *, + project_id: str, + type: BlobStorageIntegrationType, + bucket_name: str, + region: str, + export_frequency: BlobStorageExportFrequency, + enabled: bool, + force_path_style: bool, + file_type: BlobStorageIntegrationFileType, + export_mode: BlobStorageExportMode, + endpoint: typing.Optional[str] = OMIT, + access_key_id: typing.Optional[str] = OMIT, + secret_access_key: typing.Optional[str] = OMIT, + prefix: typing.Optional[str] = OMIT, + export_start_date: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> BlobStorageIntegrationResponse: + """ + Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. + + Parameters + ---------- + project_id : str + ID of the project in which to configure the blob storage integration + + type : BlobStorageIntegrationType + + bucket_name : str + Name of the storage bucket + + region : str + Storage region + + export_frequency : BlobStorageExportFrequency + + enabled : bool + Whether the integration is active + + force_path_style : bool + Use path-style URLs for S3 requests + + file_type : BlobStorageIntegrationFileType + + export_mode : BlobStorageExportMode + + endpoint : typing.Optional[str] + Custom endpoint URL (required for S3_COMPATIBLE type) + + access_key_id : typing.Optional[str] + Access key ID for authentication + + secret_access_key : typing.Optional[str] + Secret access key for authentication (will be encrypted when stored) + + prefix : typing.Optional[str] + Path prefix for exported files (must end with forward slash if provided) + + export_start_date : typing.Optional[dt.datetime] + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.blob_storage_integrations import ( + BlobStorageExportFrequency, + BlobStorageExportMode, + BlobStorageIntegrationFileType, + BlobStorageIntegrationType, + ) + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.upsert_blob_storage_integration( + project_id="projectId", + type=BlobStorageIntegrationType.S3, + bucket_name="bucketName", + region="region", + export_frequency=BlobStorageExportFrequency.HOURLY, + enabled=True, + force_path_style=True, + file_type=BlobStorageIntegrationFileType.JSON, + export_mode=BlobStorageExportMode.FULL_HISTORY, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.upsert_blob_storage_integration( + project_id=project_id, + type=type, + bucket_name=bucket_name, + region=region, + export_frequency=export_frequency, + enabled=enabled, + force_path_style=force_path_style, + file_type=file_type, + export_mode=export_mode, + endpoint=endpoint, + access_key_id=access_key_id, + secret_access_key=secret_access_key, + prefix=prefix, + export_start_date=export_start_date, + request_options=request_options, + ) + return _response.data + + async def delete_blob_storage_integration( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationDeletionResponse: + """ + Delete a blob storage integration by ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.delete_blob_storage_integration( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_blob_storage_integration( + id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py new file mode 100644 index 000000000..00ee72316 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -0,0 +1,773 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.blob_storage_export_frequency import BlobStorageExportFrequency +from .types.blob_storage_export_mode import BlobStorageExportMode +from .types.blob_storage_integration_deletion_response import ( + BlobStorageIntegrationDeletionResponse, +) +from .types.blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integration_type import BlobStorageIntegrationType +from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawBlobStorageIntegrationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_blob_storage_integrations( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[BlobStorageIntegrationsResponse]: + """ + Get all blob storage integrations for the organization (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[BlobStorageIntegrationsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationsResponse, + parse_obj_as( + type_=BlobStorageIntegrationsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def upsert_blob_storage_integration( + self, + *, + project_id: str, + type: BlobStorageIntegrationType, + bucket_name: str, + region: str, + export_frequency: BlobStorageExportFrequency, + enabled: bool, + force_path_style: bool, + file_type: BlobStorageIntegrationFileType, + export_mode: BlobStorageExportMode, + endpoint: typing.Optional[str] = OMIT, + access_key_id: typing.Optional[str] = OMIT, + secret_access_key: typing.Optional[str] = OMIT, + prefix: typing.Optional[str] = OMIT, + export_start_date: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[BlobStorageIntegrationResponse]: + """ + Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. + + Parameters + ---------- + project_id : str + ID of the project in which to configure the blob storage integration + + type : BlobStorageIntegrationType + + bucket_name : str + Name of the storage bucket + + region : str + Storage region + + export_frequency : BlobStorageExportFrequency + + enabled : bool + Whether the integration is active + + force_path_style : bool + Use path-style URLs for S3 requests + + file_type : BlobStorageIntegrationFileType + + export_mode : BlobStorageExportMode + + endpoint : typing.Optional[str] + Custom endpoint URL (required for S3_COMPATIBLE type) + + access_key_id : typing.Optional[str] + Access key ID for authentication + + secret_access_key : typing.Optional[str] + Secret access key for authentication (will be encrypted when stored) + + prefix : typing.Optional[str] + Path prefix for exported files (must end with forward slash if provided) + + export_start_date : typing.Optional[dt.datetime] + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[BlobStorageIntegrationResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="PUT", + json={ + "projectId": project_id, + "type": type, + "bucketName": bucket_name, + "endpoint": endpoint, + "region": region, + "accessKeyId": access_key_id, + "secretAccessKey": secret_access_key, + "prefix": prefix, + "exportFrequency": export_frequency, + "enabled": enabled, + "forcePathStyle": force_path_style, + "fileType": file_type, + "exportMode": export_mode, + "exportStartDate": export_start_date, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationResponse, + parse_obj_as( + type_=BlobStorageIntegrationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_blob_storage_integration( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[BlobStorageIntegrationDeletionResponse]: + """ + Delete a blob storage integration by ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[BlobStorageIntegrationDeletionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationDeletionResponse, + parse_obj_as( + type_=BlobStorageIntegrationDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawBlobStorageIntegrationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_blob_storage_integrations( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[BlobStorageIntegrationsResponse]: + """ + Get all blob storage integrations for the organization (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[BlobStorageIntegrationsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationsResponse, + parse_obj_as( + type_=BlobStorageIntegrationsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def upsert_blob_storage_integration( + self, + *, + project_id: str, + type: BlobStorageIntegrationType, + bucket_name: str, + region: str, + export_frequency: BlobStorageExportFrequency, + enabled: bool, + force_path_style: bool, + file_type: BlobStorageIntegrationFileType, + export_mode: BlobStorageExportMode, + endpoint: typing.Optional[str] = OMIT, + access_key_id: typing.Optional[str] = OMIT, + secret_access_key: typing.Optional[str] = OMIT, + prefix: typing.Optional[str] = OMIT, + export_start_date: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[BlobStorageIntegrationResponse]: + """ + Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. + + Parameters + ---------- + project_id : str + ID of the project in which to configure the blob storage integration + + type : BlobStorageIntegrationType + + bucket_name : str + Name of the storage bucket + + region : str + Storage region + + export_frequency : BlobStorageExportFrequency + + enabled : bool + Whether the integration is active + + force_path_style : bool + Use path-style URLs for S3 requests + + file_type : BlobStorageIntegrationFileType + + export_mode : BlobStorageExportMode + + endpoint : typing.Optional[str] + Custom endpoint URL (required for S3_COMPATIBLE type) + + access_key_id : typing.Optional[str] + Access key ID for authentication + + secret_access_key : typing.Optional[str] + Secret access key for authentication (will be encrypted when stored) + + prefix : typing.Optional[str] + Path prefix for exported files (must end with forward slash if provided) + + export_start_date : typing.Optional[dt.datetime] + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[BlobStorageIntegrationResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/integrations/blob-storage", + method="PUT", + json={ + "projectId": project_id, + "type": type, + "bucketName": bucket_name, + "endpoint": endpoint, + "region": region, + "accessKeyId": access_key_id, + "secretAccessKey": secret_access_key, + "prefix": prefix, + "exportFrequency": export_frequency, + "enabled": enabled, + "forcePathStyle": force_path_style, + "fileType": file_type, + "exportMode": export_mode, + "exportStartDate": export_start_date, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationResponse, + parse_obj_as( + type_=BlobStorageIntegrationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_blob_storage_integration( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[BlobStorageIntegrationDeletionResponse]: + """ + Delete a blob storage integration by ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[BlobStorageIntegrationDeletionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationDeletionResponse, + parse_obj_as( + type_=BlobStorageIntegrationDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/blob_storage_integrations/types/__init__.py b/langfuse/api/blob_storage_integrations/types/__init__.py new file mode 100644 index 000000000..cc19f1a6d --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/__init__.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .blob_storage_export_frequency import BlobStorageExportFrequency + from .blob_storage_export_mode import BlobStorageExportMode + from .blob_storage_integration_deletion_response import ( + BlobStorageIntegrationDeletionResponse, + ) + from .blob_storage_integration_file_type import BlobStorageIntegrationFileType + from .blob_storage_integration_response import BlobStorageIntegrationResponse + from .blob_storage_integration_type import BlobStorageIntegrationType + from .blob_storage_integrations_response import BlobStorageIntegrationsResponse + from .create_blob_storage_integration_request import ( + CreateBlobStorageIntegrationRequest, + ) +_dynamic_imports: typing.Dict[str, str] = { + "BlobStorageExportFrequency": ".blob_storage_export_frequency", + "BlobStorageExportMode": ".blob_storage_export_mode", + "BlobStorageIntegrationDeletionResponse": ".blob_storage_integration_deletion_response", + "BlobStorageIntegrationFileType": ".blob_storage_integration_file_type", + "BlobStorageIntegrationResponse": ".blob_storage_integration_response", + "BlobStorageIntegrationType": ".blob_storage_integration_type", + "BlobStorageIntegrationsResponse": ".blob_storage_integrations_response", + "CreateBlobStorageIntegrationRequest": ".create_blob_storage_integration_request", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BlobStorageExportFrequency", + "BlobStorageExportMode", + "BlobStorageIntegrationDeletionResponse", + "BlobStorageIntegrationFileType", + "BlobStorageIntegrationResponse", + "BlobStorageIntegrationType", + "BlobStorageIntegrationsResponse", + "CreateBlobStorageIntegrationRequest", +] diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py b/langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py similarity index 89% rename from langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py rename to langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py index 936e0c18f..bcc7fc6d5 100644 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_frequency.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class BlobStorageExportFrequency(str, enum.Enum): +class BlobStorageExportFrequency(enum.StrEnum): HOURLY = "hourly" DAILY = "daily" WEEKLY = "weekly" diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py b/langfuse/api/blob_storage_integrations/types/blob_storage_export_mode.py similarity index 91% rename from langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py rename to langfuse/api/blob_storage_integrations/types/blob_storage_export_mode.py index 1eafab79d..d692c0fb9 100644 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_export_mode.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_export_mode.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class BlobStorageExportMode(str, enum.Enum): +class BlobStorageExportMode(enum.StrEnum): FULL_HISTORY = "FULL_HISTORY" FROM_TODAY = "FROM_TODAY" FROM_CUSTOM_DATE = "FROM_CUSTOM_DATE" diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py new file mode 100644 index 000000000..d0c8655b1 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class BlobStorageIntegrationDeletionResponse(UniversalBaseModel): + message: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_file_type.py similarity index 88% rename from langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py rename to langfuse/api/blob_storage_integrations/types/blob_storage_integration_file_type.py index a63631c6f..52998e5e4 100644 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_file_type.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_file_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class BlobStorageIntegrationFileType(str, enum.Enum): +class BlobStorageIntegrationFileType(enum.StrEnum): JSON = "JSON" CSV = "CSV" JSONL = "JSONL" diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py new file mode 100644 index 000000000..08529ee67 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .blob_storage_export_frequency import BlobStorageExportFrequency +from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .blob_storage_integration_type import BlobStorageIntegrationType + + +class BlobStorageIntegrationResponse(UniversalBaseModel): + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + type: BlobStorageIntegrationType + bucket_name: typing_extensions.Annotated[str, FieldMetadata(alias="bucketName")] + endpoint: typing.Optional[str] = None + region: str + access_key_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="accessKeyId") + ] = None + prefix: str + export_frequency: typing_extensions.Annotated[ + BlobStorageExportFrequency, FieldMetadata(alias="exportFrequency") + ] + enabled: bool + force_path_style: typing_extensions.Annotated[ + bool, FieldMetadata(alias="forcePathStyle") + ] + file_type: typing_extensions.Annotated[ + BlobStorageIntegrationFileType, FieldMetadata(alias="fileType") + ] + export_mode: typing_extensions.Annotated[ + BlobStorageExportMode, FieldMetadata(alias="exportMode") + ] + export_start_date: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="exportStartDate") + ] = None + next_sync_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="nextSyncAt") + ] = None + last_sync_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastSyncAt") + ] = None + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_type.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_type.py new file mode 100644 index 000000000..66828a62d --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_type.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageIntegrationType(enum.StrEnum): + S3 = "S3" + S3COMPATIBLE = "S3_COMPATIBLE" + AZURE_BLOB_STORAGE = "AZURE_BLOB_STORAGE" + + def visit( + self, + s3: typing.Callable[[], T_Result], + s3compatible: typing.Callable[[], T_Result], + azure_blob_storage: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageIntegrationType.S3: + return s3() + if self is BlobStorageIntegrationType.S3COMPATIBLE: + return s3compatible() + if self is BlobStorageIntegrationType.AZURE_BLOB_STORAGE: + return azure_blob_storage() diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integrations_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integrations_response.py new file mode 100644 index 000000000..0f3f5cdd7 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integrations_response.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .blob_storage_integration_response import BlobStorageIntegrationResponse + + +class BlobStorageIntegrationsResponse(UniversalBaseModel): + data: typing.List[BlobStorageIntegrationResponse] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py new file mode 100644 index 000000000..3a4d9fa06 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py @@ -0,0 +1,91 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .blob_storage_export_frequency import BlobStorageExportFrequency +from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .blob_storage_integration_type import BlobStorageIntegrationType + + +class CreateBlobStorageIntegrationRequest(UniversalBaseModel): + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] = ( + pydantic.Field() + ) + """ + ID of the project in which to configure the blob storage integration + """ + + type: BlobStorageIntegrationType + bucket_name: typing_extensions.Annotated[str, FieldMetadata(alias="bucketName")] = ( + pydantic.Field() + ) + """ + Name of the storage bucket + """ + + endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + Custom endpoint URL (required for S3_COMPATIBLE type) + """ + + region: str = pydantic.Field() + """ + Storage region + """ + + access_key_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="accessKeyId") + ] = pydantic.Field(default=None) + """ + Access key ID for authentication + """ + + secret_access_key: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="secretAccessKey") + ] = pydantic.Field(default=None) + """ + Secret access key for authentication (will be encrypted when stored) + """ + + prefix: typing.Optional[str] = pydantic.Field(default=None) + """ + Path prefix for exported files (must end with forward slash if provided) + """ + + export_frequency: typing_extensions.Annotated[ + BlobStorageExportFrequency, FieldMetadata(alias="exportFrequency") + ] + enabled: bool = pydantic.Field() + """ + Whether the integration is active + """ + + force_path_style: typing_extensions.Annotated[ + bool, FieldMetadata(alias="forcePathStyle") + ] = pydantic.Field() + """ + Use path-style URLs for S3 requests + """ + + file_type: typing_extensions.Annotated[ + BlobStorageIntegrationFileType, FieldMetadata(alias="fileType") + ] + export_mode: typing_extensions.Annotated[ + BlobStorageExportMode, FieldMetadata(alias="exportMode") + ] + export_start_date: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="exportStartDate") + ] = pydantic.Field(default=None) + """ + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 09674e979..041f214b9 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -1,63 +1,51 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations + import typing import httpx - from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .resources.annotation_queues.client import ( - AnnotationQueuesClient, - AsyncAnnotationQueuesClient, -) -from .resources.blob_storage_integrations.client import ( - AsyncBlobStorageIntegrationsClient, - BlobStorageIntegrationsClient, -) -from .resources.comments.client import AsyncCommentsClient, CommentsClient -from .resources.dataset_items.client import AsyncDatasetItemsClient, DatasetItemsClient -from .resources.dataset_run_items.client import ( - AsyncDatasetRunItemsClient, - DatasetRunItemsClient, -) -from .resources.datasets.client import AsyncDatasetsClient, DatasetsClient -from .resources.health.client import AsyncHealthClient, HealthClient -from .resources.ingestion.client import AsyncIngestionClient, IngestionClient -from .resources.llm_connections.client import ( - AsyncLlmConnectionsClient, - LlmConnectionsClient, -) -from .resources.media.client import AsyncMediaClient, MediaClient -from .resources.metrics.client import AsyncMetricsClient, MetricsClient -from .resources.metrics_v_2.client import AsyncMetricsV2Client, MetricsV2Client -from .resources.models.client import AsyncModelsClient, ModelsClient -from .resources.observations.client import AsyncObservationsClient, ObservationsClient -from .resources.observations_v_2.client import ( - AsyncObservationsV2Client, - ObservationsV2Client, -) -from .resources.opentelemetry.client import ( - AsyncOpentelemetryClient, - OpentelemetryClient, -) -from .resources.organizations.client import ( - AsyncOrganizationsClient, - OrganizationsClient, -) -from .resources.projects.client import AsyncProjectsClient, ProjectsClient -from .resources.prompt_version.client import ( - AsyncPromptVersionClient, - PromptVersionClient, -) -from .resources.prompts.client import AsyncPromptsClient, PromptsClient -from .resources.scim.client import AsyncScimClient, ScimClient -from .resources.score.client import AsyncScoreClient, ScoreClient -from .resources.score_configs.client import AsyncScoreConfigsClient, ScoreConfigsClient -from .resources.score_v_2.client import AsyncScoreV2Client, ScoreV2Client -from .resources.sessions.client import AsyncSessionsClient, SessionsClient -from .resources.trace.client import AsyncTraceClient, TraceClient - - -class FernLangfuse: + +if typing.TYPE_CHECKING: + from .annotation_queues.client import ( + AnnotationQueuesClient, + AsyncAnnotationQueuesClient, + ) + from .blob_storage_integrations.client import ( + AsyncBlobStorageIntegrationsClient, + BlobStorageIntegrationsClient, + ) + from .comments.client import AsyncCommentsClient, CommentsClient + from .dataset_items.client import AsyncDatasetItemsClient, DatasetItemsClient + from .dataset_run_items.client import ( + AsyncDatasetRunItemsClient, + DatasetRunItemsClient, + ) + from .datasets.client import AsyncDatasetsClient, DatasetsClient + from .health.client import AsyncHealthClient, HealthClient + from .ingestion.client import AsyncIngestionClient, IngestionClient + from .llm_connections.client import AsyncLlmConnectionsClient, LlmConnectionsClient + from .media.client import AsyncMediaClient, MediaClient + from .metrics.client import AsyncMetricsClient, MetricsClient + from .metrics_v2.client import AsyncMetricsV2Client, MetricsV2Client + from .models.client import AsyncModelsClient, ModelsClient + from .observations.client import AsyncObservationsClient, ObservationsClient + from .observations_v2.client import AsyncObservationsV2Client, ObservationsV2Client + from .opentelemetry.client import AsyncOpentelemetryClient, OpentelemetryClient + from .organizations.client import AsyncOrganizationsClient, OrganizationsClient + from .projects.client import AsyncProjectsClient, ProjectsClient + from .prompt_version.client import AsyncPromptVersionClient, PromptVersionClient + from .prompts.client import AsyncPromptsClient, PromptsClient + from .scim.client import AsyncScimClient, ScimClient + from .score.client import AsyncScoreClient, ScoreClient + from .score_configs.client import AsyncScoreConfigsClient, ScoreConfigsClient + from .score_v2.client import AsyncScoreV2Client, ScoreV2Client + from .sessions.client import AsyncSessionsClient, SessionsClient + from .trace.client import AsyncTraceClient, TraceClient + + +class LangfuseAPI: """ Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. @@ -71,6 +59,9 @@ class FernLangfuse: x_langfuse_public_key : typing.Optional[str] username : typing.Optional[typing.Union[str, typing.Callable[[], str]]] password : typing.Optional[typing.Union[str, typing.Callable[[], str]]] + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + timeout : typing.Optional[float] The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. @@ -82,9 +73,9 @@ class FernLangfuse: Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -103,12 +94,17 @@ def __init__( x_langfuse_public_key: typing.Optional[str] = None, username: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.Client] = None, ): _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else None + timeout + if timeout is not None + else 60 + if httpx_client is None + else httpx_client.timeout.read ) self._client_wrapper = SyncClientWrapper( base_url=base_url, @@ -117,6 +113,7 @@ def __init__( x_langfuse_public_key=x_langfuse_public_key, username=username, password=password, + headers=headers, httpx_client=httpx_client if httpx_client is not None else httpx.Client( @@ -126,43 +123,265 @@ def __init__( else httpx.Client(timeout=_defaulted_timeout), timeout=_defaulted_timeout, ) - self.annotation_queues = AnnotationQueuesClient( - client_wrapper=self._client_wrapper - ) - self.blob_storage_integrations = BlobStorageIntegrationsClient( - client_wrapper=self._client_wrapper - ) - self.comments = CommentsClient(client_wrapper=self._client_wrapper) - self.dataset_items = DatasetItemsClient(client_wrapper=self._client_wrapper) - self.dataset_run_items = DatasetRunItemsClient( - client_wrapper=self._client_wrapper - ) - self.datasets = DatasetsClient(client_wrapper=self._client_wrapper) - self.health = HealthClient(client_wrapper=self._client_wrapper) - self.ingestion = IngestionClient(client_wrapper=self._client_wrapper) - self.llm_connections = LlmConnectionsClient(client_wrapper=self._client_wrapper) - self.media = MediaClient(client_wrapper=self._client_wrapper) - self.metrics_v_2 = MetricsV2Client(client_wrapper=self._client_wrapper) - self.metrics = MetricsClient(client_wrapper=self._client_wrapper) - self.models = ModelsClient(client_wrapper=self._client_wrapper) - self.observations_v_2 = ObservationsV2Client( - client_wrapper=self._client_wrapper - ) - self.observations = ObservationsClient(client_wrapper=self._client_wrapper) - self.opentelemetry = OpentelemetryClient(client_wrapper=self._client_wrapper) - self.organizations = OrganizationsClient(client_wrapper=self._client_wrapper) - self.projects = ProjectsClient(client_wrapper=self._client_wrapper) - self.prompt_version = PromptVersionClient(client_wrapper=self._client_wrapper) - self.prompts = PromptsClient(client_wrapper=self._client_wrapper) - self.scim = ScimClient(client_wrapper=self._client_wrapper) - self.score_configs = ScoreConfigsClient(client_wrapper=self._client_wrapper) - self.score_v_2 = ScoreV2Client(client_wrapper=self._client_wrapper) - self.score = ScoreClient(client_wrapper=self._client_wrapper) - self.sessions = SessionsClient(client_wrapper=self._client_wrapper) - self.trace = TraceClient(client_wrapper=self._client_wrapper) - - -class AsyncFernLangfuse: + self._annotation_queues: typing.Optional[AnnotationQueuesClient] = None + self._blob_storage_integrations: typing.Optional[ + BlobStorageIntegrationsClient + ] = None + self._comments: typing.Optional[CommentsClient] = None + self._dataset_items: typing.Optional[DatasetItemsClient] = None + self._dataset_run_items: typing.Optional[DatasetRunItemsClient] = None + self._datasets: typing.Optional[DatasetsClient] = None + self._health: typing.Optional[HealthClient] = None + self._ingestion: typing.Optional[IngestionClient] = None + self._llm_connections: typing.Optional[LlmConnectionsClient] = None + self._media: typing.Optional[MediaClient] = None + self._metrics_v2: typing.Optional[MetricsV2Client] = None + self._metrics: typing.Optional[MetricsClient] = None + self._models: typing.Optional[ModelsClient] = None + self._observations_v2: typing.Optional[ObservationsV2Client] = None + self._observations: typing.Optional[ObservationsClient] = None + self._opentelemetry: typing.Optional[OpentelemetryClient] = None + self._organizations: typing.Optional[OrganizationsClient] = None + self._projects: typing.Optional[ProjectsClient] = None + self._prompt_version: typing.Optional[PromptVersionClient] = None + self._prompts: typing.Optional[PromptsClient] = None + self._scim: typing.Optional[ScimClient] = None + self._score_configs: typing.Optional[ScoreConfigsClient] = None + self._score_v2: typing.Optional[ScoreV2Client] = None + self._score: typing.Optional[ScoreClient] = None + self._sessions: typing.Optional[SessionsClient] = None + self._trace: typing.Optional[TraceClient] = None + + @property + def annotation_queues(self): + if self._annotation_queues is None: + from .annotation_queues.client import AnnotationQueuesClient # noqa: E402 + + self._annotation_queues = AnnotationQueuesClient( + client_wrapper=self._client_wrapper + ) + return self._annotation_queues + + @property + def blob_storage_integrations(self): + if self._blob_storage_integrations is None: + from .blob_storage_integrations.client import BlobStorageIntegrationsClient # noqa: E402 + + self._blob_storage_integrations = BlobStorageIntegrationsClient( + client_wrapper=self._client_wrapper + ) + return self._blob_storage_integrations + + @property + def comments(self): + if self._comments is None: + from .comments.client import CommentsClient # noqa: E402 + + self._comments = CommentsClient(client_wrapper=self._client_wrapper) + return self._comments + + @property + def dataset_items(self): + if self._dataset_items is None: + from .dataset_items.client import DatasetItemsClient # noqa: E402 + + self._dataset_items = DatasetItemsClient( + client_wrapper=self._client_wrapper + ) + return self._dataset_items + + @property + def dataset_run_items(self): + if self._dataset_run_items is None: + from .dataset_run_items.client import DatasetRunItemsClient # noqa: E402 + + self._dataset_run_items = DatasetRunItemsClient( + client_wrapper=self._client_wrapper + ) + return self._dataset_run_items + + @property + def datasets(self): + if self._datasets is None: + from .datasets.client import DatasetsClient # noqa: E402 + + self._datasets = DatasetsClient(client_wrapper=self._client_wrapper) + return self._datasets + + @property + def health(self): + if self._health is None: + from .health.client import HealthClient # noqa: E402 + + self._health = HealthClient(client_wrapper=self._client_wrapper) + return self._health + + @property + def ingestion(self): + if self._ingestion is None: + from .ingestion.client import IngestionClient # noqa: E402 + + self._ingestion = IngestionClient(client_wrapper=self._client_wrapper) + return self._ingestion + + @property + def llm_connections(self): + if self._llm_connections is None: + from .llm_connections.client import LlmConnectionsClient # noqa: E402 + + self._llm_connections = LlmConnectionsClient( + client_wrapper=self._client_wrapper + ) + return self._llm_connections + + @property + def media(self): + if self._media is None: + from .media.client import MediaClient # noqa: E402 + + self._media = MediaClient(client_wrapper=self._client_wrapper) + return self._media + + @property + def metrics_v2(self): + if self._metrics_v2 is None: + from .metrics_v2.client import MetricsV2Client # noqa: E402 + + self._metrics_v2 = MetricsV2Client(client_wrapper=self._client_wrapper) + return self._metrics_v2 + + @property + def metrics(self): + if self._metrics is None: + from .metrics.client import MetricsClient # noqa: E402 + + self._metrics = MetricsClient(client_wrapper=self._client_wrapper) + return self._metrics + + @property + def models(self): + if self._models is None: + from .models.client import ModelsClient # noqa: E402 + + self._models = ModelsClient(client_wrapper=self._client_wrapper) + return self._models + + @property + def observations_v2(self): + if self._observations_v2 is None: + from .observations_v2.client import ObservationsV2Client # noqa: E402 + + self._observations_v2 = ObservationsV2Client( + client_wrapper=self._client_wrapper + ) + return self._observations_v2 + + @property + def observations(self): + if self._observations is None: + from .observations.client import ObservationsClient # noqa: E402 + + self._observations = ObservationsClient(client_wrapper=self._client_wrapper) + return self._observations + + @property + def opentelemetry(self): + if self._opentelemetry is None: + from .opentelemetry.client import OpentelemetryClient # noqa: E402 + + self._opentelemetry = OpentelemetryClient( + client_wrapper=self._client_wrapper + ) + return self._opentelemetry + + @property + def organizations(self): + if self._organizations is None: + from .organizations.client import OrganizationsClient # noqa: E402 + + self._organizations = OrganizationsClient( + client_wrapper=self._client_wrapper + ) + return self._organizations + + @property + def projects(self): + if self._projects is None: + from .projects.client import ProjectsClient # noqa: E402 + + self._projects = ProjectsClient(client_wrapper=self._client_wrapper) + return self._projects + + @property + def prompt_version(self): + if self._prompt_version is None: + from .prompt_version.client import PromptVersionClient # noqa: E402 + + self._prompt_version = PromptVersionClient( + client_wrapper=self._client_wrapper + ) + return self._prompt_version + + @property + def prompts(self): + if self._prompts is None: + from .prompts.client import PromptsClient # noqa: E402 + + self._prompts = PromptsClient(client_wrapper=self._client_wrapper) + return self._prompts + + @property + def scim(self): + if self._scim is None: + from .scim.client import ScimClient # noqa: E402 + + self._scim = ScimClient(client_wrapper=self._client_wrapper) + return self._scim + + @property + def score_configs(self): + if self._score_configs is None: + from .score_configs.client import ScoreConfigsClient # noqa: E402 + + self._score_configs = ScoreConfigsClient( + client_wrapper=self._client_wrapper + ) + return self._score_configs + + @property + def score_v2(self): + if self._score_v2 is None: + from .score_v2.client import ScoreV2Client # noqa: E402 + + self._score_v2 = ScoreV2Client(client_wrapper=self._client_wrapper) + return self._score_v2 + + @property + def score(self): + if self._score is None: + from .score.client import ScoreClient # noqa: E402 + + self._score = ScoreClient(client_wrapper=self._client_wrapper) + return self._score + + @property + def sessions(self): + if self._sessions is None: + from .sessions.client import SessionsClient # noqa: E402 + + self._sessions = SessionsClient(client_wrapper=self._client_wrapper) + return self._sessions + + @property + def trace(self): + if self._trace is None: + from .trace.client import TraceClient # noqa: E402 + + self._trace = TraceClient(client_wrapper=self._client_wrapper) + return self._trace + + +class AsyncLangfuseAPI: """ Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. @@ -176,6 +395,9 @@ class AsyncFernLangfuse: x_langfuse_public_key : typing.Optional[str] username : typing.Optional[typing.Union[str, typing.Callable[[], str]]] password : typing.Optional[typing.Union[str, typing.Callable[[], str]]] + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + timeout : typing.Optional[float] The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. @@ -187,9 +409,9 @@ class AsyncFernLangfuse: Examples -------- - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -208,12 +430,17 @@ def __init__( x_langfuse_public_key: typing.Optional[str] = None, username: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.AsyncClient] = None, ): _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else None + timeout + if timeout is not None + else 60 + if httpx_client is None + else httpx_client.timeout.read ) self._client_wrapper = AsyncClientWrapper( base_url=base_url, @@ -222,6 +449,7 @@ def __init__( x_langfuse_public_key=x_langfuse_public_key, username=username, password=password, + headers=headers, httpx_client=httpx_client if httpx_client is not None else httpx.AsyncClient( @@ -231,49 +459,263 @@ def __init__( else httpx.AsyncClient(timeout=_defaulted_timeout), timeout=_defaulted_timeout, ) - self.annotation_queues = AsyncAnnotationQueuesClient( - client_wrapper=self._client_wrapper - ) - self.blob_storage_integrations = AsyncBlobStorageIntegrationsClient( - client_wrapper=self._client_wrapper - ) - self.comments = AsyncCommentsClient(client_wrapper=self._client_wrapper) - self.dataset_items = AsyncDatasetItemsClient( - client_wrapper=self._client_wrapper - ) - self.dataset_run_items = AsyncDatasetRunItemsClient( - client_wrapper=self._client_wrapper - ) - self.datasets = AsyncDatasetsClient(client_wrapper=self._client_wrapper) - self.health = AsyncHealthClient(client_wrapper=self._client_wrapper) - self.ingestion = AsyncIngestionClient(client_wrapper=self._client_wrapper) - self.llm_connections = AsyncLlmConnectionsClient( - client_wrapper=self._client_wrapper - ) - self.media = AsyncMediaClient(client_wrapper=self._client_wrapper) - self.metrics_v_2 = AsyncMetricsV2Client(client_wrapper=self._client_wrapper) - self.metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) - self.models = AsyncModelsClient(client_wrapper=self._client_wrapper) - self.observations_v_2 = AsyncObservationsV2Client( - client_wrapper=self._client_wrapper - ) - self.observations = AsyncObservationsClient(client_wrapper=self._client_wrapper) - self.opentelemetry = AsyncOpentelemetryClient( - client_wrapper=self._client_wrapper - ) - self.organizations = AsyncOrganizationsClient( - client_wrapper=self._client_wrapper - ) - self.projects = AsyncProjectsClient(client_wrapper=self._client_wrapper) - self.prompt_version = AsyncPromptVersionClient( - client_wrapper=self._client_wrapper - ) - self.prompts = AsyncPromptsClient(client_wrapper=self._client_wrapper) - self.scim = AsyncScimClient(client_wrapper=self._client_wrapper) - self.score_configs = AsyncScoreConfigsClient( - client_wrapper=self._client_wrapper - ) - self.score_v_2 = AsyncScoreV2Client(client_wrapper=self._client_wrapper) - self.score = AsyncScoreClient(client_wrapper=self._client_wrapper) - self.sessions = AsyncSessionsClient(client_wrapper=self._client_wrapper) - self.trace = AsyncTraceClient(client_wrapper=self._client_wrapper) + self._annotation_queues: typing.Optional[AsyncAnnotationQueuesClient] = None + self._blob_storage_integrations: typing.Optional[ + AsyncBlobStorageIntegrationsClient + ] = None + self._comments: typing.Optional[AsyncCommentsClient] = None + self._dataset_items: typing.Optional[AsyncDatasetItemsClient] = None + self._dataset_run_items: typing.Optional[AsyncDatasetRunItemsClient] = None + self._datasets: typing.Optional[AsyncDatasetsClient] = None + self._health: typing.Optional[AsyncHealthClient] = None + self._ingestion: typing.Optional[AsyncIngestionClient] = None + self._llm_connections: typing.Optional[AsyncLlmConnectionsClient] = None + self._media: typing.Optional[AsyncMediaClient] = None + self._metrics_v2: typing.Optional[AsyncMetricsV2Client] = None + self._metrics: typing.Optional[AsyncMetricsClient] = None + self._models: typing.Optional[AsyncModelsClient] = None + self._observations_v2: typing.Optional[AsyncObservationsV2Client] = None + self._observations: typing.Optional[AsyncObservationsClient] = None + self._opentelemetry: typing.Optional[AsyncOpentelemetryClient] = None + self._organizations: typing.Optional[AsyncOrganizationsClient] = None + self._projects: typing.Optional[AsyncProjectsClient] = None + self._prompt_version: typing.Optional[AsyncPromptVersionClient] = None + self._prompts: typing.Optional[AsyncPromptsClient] = None + self._scim: typing.Optional[AsyncScimClient] = None + self._score_configs: typing.Optional[AsyncScoreConfigsClient] = None + self._score_v2: typing.Optional[AsyncScoreV2Client] = None + self._score: typing.Optional[AsyncScoreClient] = None + self._sessions: typing.Optional[AsyncSessionsClient] = None + self._trace: typing.Optional[AsyncTraceClient] = None + + @property + def annotation_queues(self): + if self._annotation_queues is None: + from .annotation_queues.client import AsyncAnnotationQueuesClient # noqa: E402 + + self._annotation_queues = AsyncAnnotationQueuesClient( + client_wrapper=self._client_wrapper + ) + return self._annotation_queues + + @property + def blob_storage_integrations(self): + if self._blob_storage_integrations is None: + from .blob_storage_integrations.client import ( + AsyncBlobStorageIntegrationsClient, + ) # noqa: E402 + + self._blob_storage_integrations = AsyncBlobStorageIntegrationsClient( + client_wrapper=self._client_wrapper + ) + return self._blob_storage_integrations + + @property + def comments(self): + if self._comments is None: + from .comments.client import AsyncCommentsClient # noqa: E402 + + self._comments = AsyncCommentsClient(client_wrapper=self._client_wrapper) + return self._comments + + @property + def dataset_items(self): + if self._dataset_items is None: + from .dataset_items.client import AsyncDatasetItemsClient # noqa: E402 + + self._dataset_items = AsyncDatasetItemsClient( + client_wrapper=self._client_wrapper + ) + return self._dataset_items + + @property + def dataset_run_items(self): + if self._dataset_run_items is None: + from .dataset_run_items.client import AsyncDatasetRunItemsClient # noqa: E402 + + self._dataset_run_items = AsyncDatasetRunItemsClient( + client_wrapper=self._client_wrapper + ) + return self._dataset_run_items + + @property + def datasets(self): + if self._datasets is None: + from .datasets.client import AsyncDatasetsClient # noqa: E402 + + self._datasets = AsyncDatasetsClient(client_wrapper=self._client_wrapper) + return self._datasets + + @property + def health(self): + if self._health is None: + from .health.client import AsyncHealthClient # noqa: E402 + + self._health = AsyncHealthClient(client_wrapper=self._client_wrapper) + return self._health + + @property + def ingestion(self): + if self._ingestion is None: + from .ingestion.client import AsyncIngestionClient # noqa: E402 + + self._ingestion = AsyncIngestionClient(client_wrapper=self._client_wrapper) + return self._ingestion + + @property + def llm_connections(self): + if self._llm_connections is None: + from .llm_connections.client import AsyncLlmConnectionsClient # noqa: E402 + + self._llm_connections = AsyncLlmConnectionsClient( + client_wrapper=self._client_wrapper + ) + return self._llm_connections + + @property + def media(self): + if self._media is None: + from .media.client import AsyncMediaClient # noqa: E402 + + self._media = AsyncMediaClient(client_wrapper=self._client_wrapper) + return self._media + + @property + def metrics_v2(self): + if self._metrics_v2 is None: + from .metrics_v2.client import AsyncMetricsV2Client # noqa: E402 + + self._metrics_v2 = AsyncMetricsV2Client(client_wrapper=self._client_wrapper) + return self._metrics_v2 + + @property + def metrics(self): + if self._metrics is None: + from .metrics.client import AsyncMetricsClient # noqa: E402 + + self._metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) + return self._metrics + + @property + def models(self): + if self._models is None: + from .models.client import AsyncModelsClient # noqa: E402 + + self._models = AsyncModelsClient(client_wrapper=self._client_wrapper) + return self._models + + @property + def observations_v2(self): + if self._observations_v2 is None: + from .observations_v2.client import AsyncObservationsV2Client # noqa: E402 + + self._observations_v2 = AsyncObservationsV2Client( + client_wrapper=self._client_wrapper + ) + return self._observations_v2 + + @property + def observations(self): + if self._observations is None: + from .observations.client import AsyncObservationsClient # noqa: E402 + + self._observations = AsyncObservationsClient( + client_wrapper=self._client_wrapper + ) + return self._observations + + @property + def opentelemetry(self): + if self._opentelemetry is None: + from .opentelemetry.client import AsyncOpentelemetryClient # noqa: E402 + + self._opentelemetry = AsyncOpentelemetryClient( + client_wrapper=self._client_wrapper + ) + return self._opentelemetry + + @property + def organizations(self): + if self._organizations is None: + from .organizations.client import AsyncOrganizationsClient # noqa: E402 + + self._organizations = AsyncOrganizationsClient( + client_wrapper=self._client_wrapper + ) + return self._organizations + + @property + def projects(self): + if self._projects is None: + from .projects.client import AsyncProjectsClient # noqa: E402 + + self._projects = AsyncProjectsClient(client_wrapper=self._client_wrapper) + return self._projects + + @property + def prompt_version(self): + if self._prompt_version is None: + from .prompt_version.client import AsyncPromptVersionClient # noqa: E402 + + self._prompt_version = AsyncPromptVersionClient( + client_wrapper=self._client_wrapper + ) + return self._prompt_version + + @property + def prompts(self): + if self._prompts is None: + from .prompts.client import AsyncPromptsClient # noqa: E402 + + self._prompts = AsyncPromptsClient(client_wrapper=self._client_wrapper) + return self._prompts + + @property + def scim(self): + if self._scim is None: + from .scim.client import AsyncScimClient # noqa: E402 + + self._scim = AsyncScimClient(client_wrapper=self._client_wrapper) + return self._scim + + @property + def score_configs(self): + if self._score_configs is None: + from .score_configs.client import AsyncScoreConfigsClient # noqa: E402 + + self._score_configs = AsyncScoreConfigsClient( + client_wrapper=self._client_wrapper + ) + return self._score_configs + + @property + def score_v2(self): + if self._score_v2 is None: + from .score_v2.client import AsyncScoreV2Client # noqa: E402 + + self._score_v2 = AsyncScoreV2Client(client_wrapper=self._client_wrapper) + return self._score_v2 + + @property + def score(self): + if self._score is None: + from .score.client import AsyncScoreClient # noqa: E402 + + self._score = AsyncScoreClient(client_wrapper=self._client_wrapper) + return self._score + + @property + def sessions(self): + if self._sessions is None: + from .sessions.client import AsyncSessionsClient # noqa: E402 + + self._sessions = AsyncSessionsClient(client_wrapper=self._client_wrapper) + return self._sessions + + @property + def trace(self): + if self._trace is None: + from .trace.client import AsyncTraceClient # noqa: E402 + + self._trace = AsyncTraceClient(client_wrapper=self._client_wrapper) + return self._trace diff --git a/langfuse/api/comments/__init__.py b/langfuse/api/comments/__init__.py new file mode 100644 index 000000000..0588586c7 --- /dev/null +++ b/langfuse/api/comments/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateCommentRequest, CreateCommentResponse, GetCommentsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateCommentRequest": ".types", + "CreateCommentResponse": ".types", + "GetCommentsResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateCommentRequest", "CreateCommentResponse", "GetCommentsResponse"] diff --git a/langfuse/api/comments/client.py b/langfuse/api/comments/client.py new file mode 100644 index 000000000..f5e92ff36 --- /dev/null +++ b/langfuse/api/comments/client.py @@ -0,0 +1,407 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..commons.types.comment import Comment +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawCommentsClient, RawCommentsClient +from .types.create_comment_response import CreateCommentResponse +from .types.get_comments_response import GetCommentsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CommentsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCommentsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCommentsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCommentsClient + """ + return self._raw_client + + def create( + self, + *, + project_id: str, + object_type: str, + object_id: str, + content: str, + author_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateCommentResponse: + """ + Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). + + Parameters + ---------- + project_id : str + The id of the project to attach the comment to. + + object_type : str + The type of the object to attach the comment to (trace, observation, session, prompt). + + object_id : str + The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. + + content : str + The content of the comment. May include markdown. Currently limited to 5000 characters. + + author_user_id : typing.Optional[str] + The id of the user who created the comment. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateCommentResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.comments.create( + project_id="projectId", + object_type="objectType", + object_id="objectId", + content="content", + ) + """ + _response = self._raw_client.create( + project_id=project_id, + object_type=object_type, + object_id=object_id, + content=content, + author_user_id=author_user_id, + request_options=request_options, + ) + return _response.data + + def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + object_type: typing.Optional[str] = None, + object_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetCommentsResponse: + """ + Get all comments + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + object_type : typing.Optional[str] + Filter comments by object type (trace, observation, session, prompt). + + object_id : typing.Optional[str] + Filter comments by object id. If objectType is not provided, an error will be thrown. + + author_user_id : typing.Optional[str] + Filter comments by author user id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetCommentsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.comments.get() + """ + _response = self._raw_client.get( + page=page, + limit=limit, + object_type=object_type, + object_id=object_id, + author_user_id=author_user_id, + request_options=request_options, + ) + return _response.data + + def get_by_id( + self, + comment_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> Comment: + """ + Get a comment by id + + Parameters + ---------- + comment_id : str + The unique langfuse identifier of a comment + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Comment + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.comments.get_by_id( + comment_id="commentId", + ) + """ + _response = self._raw_client.get_by_id( + comment_id, request_options=request_options + ) + return _response.data + + +class AsyncCommentsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCommentsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCommentsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCommentsClient + """ + return self._raw_client + + async def create( + self, + *, + project_id: str, + object_type: str, + object_id: str, + content: str, + author_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateCommentResponse: + """ + Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). + + Parameters + ---------- + project_id : str + The id of the project to attach the comment to. + + object_type : str + The type of the object to attach the comment to (trace, observation, session, prompt). + + object_id : str + The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. + + content : str + The content of the comment. May include markdown. Currently limited to 5000 characters. + + author_user_id : typing.Optional[str] + The id of the user who created the comment. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateCommentResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.comments.create( + project_id="projectId", + object_type="objectType", + object_id="objectId", + content="content", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + project_id=project_id, + object_type=object_type, + object_id=object_id, + content=content, + author_user_id=author_user_id, + request_options=request_options, + ) + return _response.data + + async def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + object_type: typing.Optional[str] = None, + object_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetCommentsResponse: + """ + Get all comments + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + object_type : typing.Optional[str] + Filter comments by object type (trace, observation, session, prompt). + + object_id : typing.Optional[str] + Filter comments by object id. If objectType is not provided, an error will be thrown. + + author_user_id : typing.Optional[str] + Filter comments by author user id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetCommentsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.comments.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + page=page, + limit=limit, + object_type=object_type, + object_id=object_id, + author_user_id=author_user_id, + request_options=request_options, + ) + return _response.data + + async def get_by_id( + self, + comment_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> Comment: + """ + Get a comment by id + + Parameters + ---------- + comment_id : str + The unique langfuse identifier of a comment + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Comment + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.comments.get_by_id( + comment_id="commentId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_by_id( + comment_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/comments/raw_client.py b/langfuse/api/comments/raw_client.py new file mode 100644 index 000000000..0bb39539a --- /dev/null +++ b/langfuse/api/comments/raw_client.py @@ -0,0 +1,750 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.comment import Comment +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.create_comment_response import CreateCommentResponse +from .types.get_comments_response import GetCommentsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCommentsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + project_id: str, + object_type: str, + object_id: str, + content: str, + author_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateCommentResponse]: + """ + Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). + + Parameters + ---------- + project_id : str + The id of the project to attach the comment to. + + object_type : str + The type of the object to attach the comment to (trace, observation, session, prompt). + + object_id : str + The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. + + content : str + The content of the comment. May include markdown. Currently limited to 5000 characters. + + author_user_id : typing.Optional[str] + The id of the user who created the comment. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateCommentResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/comments", + method="POST", + json={ + "projectId": project_id, + "objectType": object_type, + "objectId": object_id, + "content": content, + "authorUserId": author_user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateCommentResponse, + parse_obj_as( + type_=CreateCommentResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + object_type: typing.Optional[str] = None, + object_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GetCommentsResponse]: + """ + Get all comments + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + object_type : typing.Optional[str] + Filter comments by object type (trace, observation, session, prompt). + + object_id : typing.Optional[str] + Filter comments by object id. If objectType is not provided, an error will be thrown. + + author_user_id : typing.Optional[str] + Filter comments by author user id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetCommentsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/comments", + method="GET", + params={ + "page": page, + "limit": limit, + "objectType": object_type, + "objectId": object_id, + "authorUserId": author_user_id, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetCommentsResponse, + parse_obj_as( + type_=GetCommentsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_by_id( + self, + comment_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Comment]: + """ + Get a comment by id + + Parameters + ---------- + comment_id : str + The unique langfuse identifier of a comment + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Comment] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/comments/{jsonable_encoder(comment_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Comment, + parse_obj_as( + type_=Comment, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawCommentsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + project_id: str, + object_type: str, + object_id: str, + content: str, + author_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateCommentResponse]: + """ + Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). + + Parameters + ---------- + project_id : str + The id of the project to attach the comment to. + + object_type : str + The type of the object to attach the comment to (trace, observation, session, prompt). + + object_id : str + The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. + + content : str + The content of the comment. May include markdown. Currently limited to 5000 characters. + + author_user_id : typing.Optional[str] + The id of the user who created the comment. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateCommentResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/comments", + method="POST", + json={ + "projectId": project_id, + "objectType": object_type, + "objectId": object_id, + "content": content, + "authorUserId": author_user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateCommentResponse, + parse_obj_as( + type_=CreateCommentResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + object_type: typing.Optional[str] = None, + object_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GetCommentsResponse]: + """ + Get all comments + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + object_type : typing.Optional[str] + Filter comments by object type (trace, observation, session, prompt). + + object_id : typing.Optional[str] + Filter comments by object id. If objectType is not provided, an error will be thrown. + + author_user_id : typing.Optional[str] + Filter comments by author user id. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetCommentsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/comments", + method="GET", + params={ + "page": page, + "limit": limit, + "objectType": object_type, + "objectId": object_id, + "authorUserId": author_user_id, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetCommentsResponse, + parse_obj_as( + type_=GetCommentsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_by_id( + self, + comment_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Comment]: + """ + Get a comment by id + + Parameters + ---------- + comment_id : str + The unique langfuse identifier of a comment + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Comment] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/comments/{jsonable_encoder(comment_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Comment, + parse_obj_as( + type_=Comment, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/comments/types/__init__.py b/langfuse/api/comments/types/__init__.py new file mode 100644 index 000000000..4936025a0 --- /dev/null +++ b/langfuse/api/comments/types/__init__.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_comment_request import CreateCommentRequest + from .create_comment_response import CreateCommentResponse + from .get_comments_response import GetCommentsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateCommentRequest": ".create_comment_request", + "CreateCommentResponse": ".create_comment_response", + "GetCommentsResponse": ".get_comments_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateCommentRequest", "CreateCommentResponse", "GetCommentsResponse"] diff --git a/langfuse/api/comments/types/create_comment_request.py b/langfuse/api/comments/types/create_comment_request.py new file mode 100644 index 000000000..56ef2794d --- /dev/null +++ b/langfuse/api/comments/types/create_comment_request.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateCommentRequest(UniversalBaseModel): + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] = ( + pydantic.Field() + ) + """ + The id of the project to attach the comment to. + """ + + object_type: typing_extensions.Annotated[str, FieldMetadata(alias="objectType")] = ( + pydantic.Field() + ) + """ + The type of the object to attach the comment to (trace, observation, session, prompt). + """ + + object_id: typing_extensions.Annotated[str, FieldMetadata(alias="objectId")] = ( + pydantic.Field() + ) + """ + The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. + """ + + content: str = pydantic.Field() + """ + The content of the comment. May include markdown. Currently limited to 5000 characters. + """ + + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = pydantic.Field(default=None) + """ + The id of the user who created the comment. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/comments/types/create_comment_response.py b/langfuse/api/comments/types/create_comment_response.py new file mode 100644 index 000000000..d080349b0 --- /dev/null +++ b/langfuse/api/comments/types/create_comment_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class CreateCommentResponse(UniversalBaseModel): + id: str = pydantic.Field() + """ + The id of the created object in Langfuse + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/comments/types/get_comments_response.py b/langfuse/api/comments/types/get_comments_response.py new file mode 100644 index 000000000..f275210e8 --- /dev/null +++ b/langfuse/api/comments/types/get_comments_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.comment import Comment +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class GetCommentsResponse(UniversalBaseModel): + data: typing.List[Comment] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/__init__.py b/langfuse/api/commons/__init__.py new file mode 100644 index 000000000..43e4649d2 --- /dev/null +++ b/langfuse/api/commons/__init__.py @@ -0,0 +1,207 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + BaseScore, + BaseScoreV1, + BooleanScore, + BooleanScoreV1, + CategoricalScore, + CategoricalScoreV1, + Comment, + CommentObjectType, + ConfigCategory, + CorrectionScore, + CreateScoreValue, + Dataset, + DatasetItem, + DatasetRun, + DatasetRunItem, + DatasetRunWithItems, + DatasetStatus, + MapValue, + Model, + ModelPrice, + ModelUsageUnit, + NumericScore, + NumericScoreV1, + Observation, + ObservationLevel, + ObservationsView, + PricingTier, + PricingTierCondition, + PricingTierInput, + PricingTierOperator, + Score, + ScoreConfig, + ScoreConfigDataType, + ScoreDataType, + ScoreSource, + ScoreV1, + ScoreV1_Boolean, + ScoreV1_Categorical, + ScoreV1_Numeric, + Score_Boolean, + Score_Categorical, + Score_Correction, + Score_Numeric, + Session, + SessionWithTraces, + Trace, + TraceWithDetails, + TraceWithFullDetails, + Usage, + ) + from .errors import ( + AccessDeniedError, + Error, + MethodNotAllowedError, + NotFoundError, + UnauthorizedError, + ) +_dynamic_imports: typing.Dict[str, str] = { + "AccessDeniedError": ".errors", + "BaseScore": ".types", + "BaseScoreV1": ".types", + "BooleanScore": ".types", + "BooleanScoreV1": ".types", + "CategoricalScore": ".types", + "CategoricalScoreV1": ".types", + "Comment": ".types", + "CommentObjectType": ".types", + "ConfigCategory": ".types", + "CorrectionScore": ".types", + "CreateScoreValue": ".types", + "Dataset": ".types", + "DatasetItem": ".types", + "DatasetRun": ".types", + "DatasetRunItem": ".types", + "DatasetRunWithItems": ".types", + "DatasetStatus": ".types", + "Error": ".errors", + "MapValue": ".types", + "MethodNotAllowedError": ".errors", + "Model": ".types", + "ModelPrice": ".types", + "ModelUsageUnit": ".types", + "NotFoundError": ".errors", + "NumericScore": ".types", + "NumericScoreV1": ".types", + "Observation": ".types", + "ObservationLevel": ".types", + "ObservationsView": ".types", + "PricingTier": ".types", + "PricingTierCondition": ".types", + "PricingTierInput": ".types", + "PricingTierOperator": ".types", + "Score": ".types", + "ScoreConfig": ".types", + "ScoreConfigDataType": ".types", + "ScoreDataType": ".types", + "ScoreSource": ".types", + "ScoreV1": ".types", + "ScoreV1_Boolean": ".types", + "ScoreV1_Categorical": ".types", + "ScoreV1_Numeric": ".types", + "Score_Boolean": ".types", + "Score_Categorical": ".types", + "Score_Correction": ".types", + "Score_Numeric": ".types", + "Session": ".types", + "SessionWithTraces": ".types", + "Trace": ".types", + "TraceWithDetails": ".types", + "TraceWithFullDetails": ".types", + "UnauthorizedError": ".errors", + "Usage": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccessDeniedError", + "BaseScore", + "BaseScoreV1", + "BooleanScore", + "BooleanScoreV1", + "CategoricalScore", + "CategoricalScoreV1", + "Comment", + "CommentObjectType", + "ConfigCategory", + "CorrectionScore", + "CreateScoreValue", + "Dataset", + "DatasetItem", + "DatasetRun", + "DatasetRunItem", + "DatasetRunWithItems", + "DatasetStatus", + "Error", + "MapValue", + "MethodNotAllowedError", + "Model", + "ModelPrice", + "ModelUsageUnit", + "NotFoundError", + "NumericScore", + "NumericScoreV1", + "Observation", + "ObservationLevel", + "ObservationsView", + "PricingTier", + "PricingTierCondition", + "PricingTierInput", + "PricingTierOperator", + "Score", + "ScoreConfig", + "ScoreConfigDataType", + "ScoreDataType", + "ScoreSource", + "ScoreV1", + "ScoreV1_Boolean", + "ScoreV1_Categorical", + "ScoreV1_Numeric", + "Score_Boolean", + "Score_Categorical", + "Score_Correction", + "Score_Numeric", + "Session", + "SessionWithTraces", + "Trace", + "TraceWithDetails", + "TraceWithFullDetails", + "UnauthorizedError", + "Usage", +] diff --git a/langfuse/api/commons/errors/__init__.py b/langfuse/api/commons/errors/__init__.py new file mode 100644 index 000000000..c633139f0 --- /dev/null +++ b/langfuse/api/commons/errors/__init__.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .access_denied_error import AccessDeniedError + from .error import Error + from .method_not_allowed_error import MethodNotAllowedError + from .not_found_error import NotFoundError + from .unauthorized_error import UnauthorizedError +_dynamic_imports: typing.Dict[str, str] = { + "AccessDeniedError": ".access_denied_error", + "Error": ".error", + "MethodNotAllowedError": ".method_not_allowed_error", + "NotFoundError": ".not_found_error", + "UnauthorizedError": ".unauthorized_error", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccessDeniedError", + "Error", + "MethodNotAllowedError", + "NotFoundError", + "UnauthorizedError", +] diff --git a/langfuse/api/commons/errors/access_denied_error.py b/langfuse/api/commons/errors/access_denied_error.py new file mode 100644 index 000000000..156403fb7 --- /dev/null +++ b/langfuse/api/commons/errors/access_denied_error.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class AccessDeniedError(ApiError): + def __init__( + self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None + ): + super().__init__(status_code=403, headers=headers, body=body) diff --git a/langfuse/api/commons/errors/error.py b/langfuse/api/commons/errors/error.py new file mode 100644 index 000000000..5a8bd9639 --- /dev/null +++ b/langfuse/api/commons/errors/error.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class Error(ApiError): + def __init__( + self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None + ): + super().__init__(status_code=400, headers=headers, body=body) diff --git a/langfuse/api/commons/errors/method_not_allowed_error.py b/langfuse/api/commons/errors/method_not_allowed_error.py new file mode 100644 index 000000000..436dd29dd --- /dev/null +++ b/langfuse/api/commons/errors/method_not_allowed_error.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class MethodNotAllowedError(ApiError): + def __init__( + self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None + ): + super().__init__(status_code=405, headers=headers, body=body) diff --git a/langfuse/api/commons/errors/not_found_error.py b/langfuse/api/commons/errors/not_found_error.py new file mode 100644 index 000000000..66b5bfc55 --- /dev/null +++ b/langfuse/api/commons/errors/not_found_error.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class NotFoundError(ApiError): + def __init__( + self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None + ): + super().__init__(status_code=404, headers=headers, body=body) diff --git a/langfuse/api/commons/errors/unauthorized_error.py b/langfuse/api/commons/errors/unauthorized_error.py new file mode 100644 index 000000000..e71a01c5d --- /dev/null +++ b/langfuse/api/commons/errors/unauthorized_error.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class UnauthorizedError(ApiError): + def __init__( + self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None + ): + super().__init__(status_code=401, headers=headers, body=body) diff --git a/langfuse/api/commons/types/__init__.py b/langfuse/api/commons/types/__init__.py new file mode 100644 index 000000000..97fc36d1d --- /dev/null +++ b/langfuse/api/commons/types/__init__.py @@ -0,0 +1,187 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .base_score import BaseScore + from .base_score_v1 import BaseScoreV1 + from .boolean_score import BooleanScore + from .boolean_score_v1 import BooleanScoreV1 + from .categorical_score import CategoricalScore + from .categorical_score_v1 import CategoricalScoreV1 + from .comment import Comment + from .comment_object_type import CommentObjectType + from .config_category import ConfigCategory + from .correction_score import CorrectionScore + from .create_score_value import CreateScoreValue + from .dataset import Dataset + from .dataset_item import DatasetItem + from .dataset_run import DatasetRun + from .dataset_run_item import DatasetRunItem + from .dataset_run_with_items import DatasetRunWithItems + from .dataset_status import DatasetStatus + from .map_value import MapValue + from .model import Model + from .model_price import ModelPrice + from .model_usage_unit import ModelUsageUnit + from .numeric_score import NumericScore + from .numeric_score_v1 import NumericScoreV1 + from .observation import Observation + from .observation_level import ObservationLevel + from .observations_view import ObservationsView + from .pricing_tier import PricingTier + from .pricing_tier_condition import PricingTierCondition + from .pricing_tier_input import PricingTierInput + from .pricing_tier_operator import PricingTierOperator + from .score import ( + Score, + Score_Boolean, + Score_Categorical, + Score_Correction, + Score_Numeric, + ) + from .score_config import ScoreConfig + from .score_config_data_type import ScoreConfigDataType + from .score_data_type import ScoreDataType + from .score_source import ScoreSource + from .score_v1 import ScoreV1, ScoreV1_Boolean, ScoreV1_Categorical, ScoreV1_Numeric + from .session import Session + from .session_with_traces import SessionWithTraces + from .trace import Trace + from .trace_with_details import TraceWithDetails + from .trace_with_full_details import TraceWithFullDetails + from .usage import Usage +_dynamic_imports: typing.Dict[str, str] = { + "BaseScore": ".base_score", + "BaseScoreV1": ".base_score_v1", + "BooleanScore": ".boolean_score", + "BooleanScoreV1": ".boolean_score_v1", + "CategoricalScore": ".categorical_score", + "CategoricalScoreV1": ".categorical_score_v1", + "Comment": ".comment", + "CommentObjectType": ".comment_object_type", + "ConfigCategory": ".config_category", + "CorrectionScore": ".correction_score", + "CreateScoreValue": ".create_score_value", + "Dataset": ".dataset", + "DatasetItem": ".dataset_item", + "DatasetRun": ".dataset_run", + "DatasetRunItem": ".dataset_run_item", + "DatasetRunWithItems": ".dataset_run_with_items", + "DatasetStatus": ".dataset_status", + "MapValue": ".map_value", + "Model": ".model", + "ModelPrice": ".model_price", + "ModelUsageUnit": ".model_usage_unit", + "NumericScore": ".numeric_score", + "NumericScoreV1": ".numeric_score_v1", + "Observation": ".observation", + "ObservationLevel": ".observation_level", + "ObservationsView": ".observations_view", + "PricingTier": ".pricing_tier", + "PricingTierCondition": ".pricing_tier_condition", + "PricingTierInput": ".pricing_tier_input", + "PricingTierOperator": ".pricing_tier_operator", + "Score": ".score", + "ScoreConfig": ".score_config", + "ScoreConfigDataType": ".score_config_data_type", + "ScoreDataType": ".score_data_type", + "ScoreSource": ".score_source", + "ScoreV1": ".score_v1", + "ScoreV1_Boolean": ".score_v1", + "ScoreV1_Categorical": ".score_v1", + "ScoreV1_Numeric": ".score_v1", + "Score_Boolean": ".score", + "Score_Categorical": ".score", + "Score_Correction": ".score", + "Score_Numeric": ".score", + "Session": ".session", + "SessionWithTraces": ".session_with_traces", + "Trace": ".trace", + "TraceWithDetails": ".trace_with_details", + "TraceWithFullDetails": ".trace_with_full_details", + "Usage": ".usage", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BaseScore", + "BaseScoreV1", + "BooleanScore", + "BooleanScoreV1", + "CategoricalScore", + "CategoricalScoreV1", + "Comment", + "CommentObjectType", + "ConfigCategory", + "CorrectionScore", + "CreateScoreValue", + "Dataset", + "DatasetItem", + "DatasetRun", + "DatasetRunItem", + "DatasetRunWithItems", + "DatasetStatus", + "MapValue", + "Model", + "ModelPrice", + "ModelUsageUnit", + "NumericScore", + "NumericScoreV1", + "Observation", + "ObservationLevel", + "ObservationsView", + "PricingTier", + "PricingTierCondition", + "PricingTierInput", + "PricingTierOperator", + "Score", + "ScoreConfig", + "ScoreConfigDataType", + "ScoreDataType", + "ScoreSource", + "ScoreV1", + "ScoreV1_Boolean", + "ScoreV1_Categorical", + "ScoreV1_Numeric", + "Score_Boolean", + "Score_Categorical", + "Score_Correction", + "Score_Numeric", + "Session", + "SessionWithTraces", + "Trace", + "TraceWithDetails", + "TraceWithFullDetails", + "Usage", +] diff --git a/langfuse/api/commons/types/base_score.py b/langfuse/api/commons/types/base_score.py new file mode 100644 index 000000000..44e09033c --- /dev/null +++ b/langfuse/api/commons/types/base_score.py @@ -0,0 +1,90 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .score_source import ScoreSource + + +class BaseScore(UniversalBaseModel): + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = pydantic.Field(default=None) + """ + The trace ID associated with the score + """ + + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = pydantic.Field(default=None) + """ + The session ID associated with the score + """ + + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = pydantic.Field(default=None) + """ + The observation ID associated with the score + """ + + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = pydantic.Field(default=None) + """ + The dataset run ID associated with the score + """ + + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = pydantic.Field(default=None) + """ + The user ID of the author + """ + + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Comment on the score + """ + + metadata: typing.Any = pydantic.Field() + """ + Metadata associated with the score + """ + + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = pydantic.Field(default=None) + """ + Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range + """ + + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = pydantic.Field(default=None) + """ + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + """ + + environment: str = pydantic.Field() + """ + The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/base_score_v1.py b/langfuse/api/commons/types/base_score_v1.py new file mode 100644 index 000000000..881b10a3b --- /dev/null +++ b/langfuse/api/commons/types/base_score_v1.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .score_source import ScoreSource + + +class BaseScoreV1(UniversalBaseModel): + id: str + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + name: str + source: ScoreSource + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = pydantic.Field(default=None) + """ + The observation ID associated with the score + """ + + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = pydantic.Field(default=None) + """ + The user ID of the author + """ + + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Comment on the score + """ + + metadata: typing.Any = pydantic.Field() + """ + Metadata associated with the score + """ + + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = pydantic.Field(default=None) + """ + Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range + """ + + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = pydantic.Field(default=None) + """ + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + """ + + environment: str = pydantic.Field() + """ + The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/boolean_score.py b/langfuse/api/commons/types/boolean_score.py new file mode 100644 index 000000000..2f65cf338 --- /dev/null +++ b/langfuse/api/commons/types/boolean_score.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score import BaseScore + + +class BooleanScore(BaseScore): + value: float = pydantic.Field() + """ + The numeric value of the score. Equals 1 for "True" and 0 for "False" + """ + + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The string representation of the score value. Is inferred from the numeric value and equals "True" or "False" + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/boolean_score_v1.py b/langfuse/api/commons/types/boolean_score_v1.py new file mode 100644 index 000000000..cf5425255 --- /dev/null +++ b/langfuse/api/commons/types/boolean_score_v1.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score_v1 import BaseScoreV1 + + +class BooleanScoreV1(BaseScoreV1): + value: float = pydantic.Field() + """ + The numeric value of the score. Equals 1 for "True" and 0 for "False" + """ + + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The string representation of the score value. Is inferred from the numeric value and equals "True" or "False" + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/categorical_score.py b/langfuse/api/commons/types/categorical_score.py new file mode 100644 index 000000000..a12ac58c3 --- /dev/null +++ b/langfuse/api/commons/types/categorical_score.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score import BaseScore + + +class CategoricalScore(BaseScore): + value: float = pydantic.Field() + """ + Represents the numeric category mapping of the stringValue. If no config is linked, defaults to 0. + """ + + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/categorical_score_v1.py b/langfuse/api/commons/types/categorical_score_v1.py new file mode 100644 index 000000000..8f98af1a8 --- /dev/null +++ b/langfuse/api/commons/types/categorical_score_v1.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score_v1 import BaseScoreV1 + + +class CategoricalScoreV1(BaseScoreV1): + value: float = pydantic.Field() + """ + Represents the numeric category mapping of the stringValue. If no config is linked, defaults to 0. + """ + + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/comment.py b/langfuse/api/commons/types/comment.py new file mode 100644 index 000000000..31daeeed8 --- /dev/null +++ b/langfuse/api/commons/types/comment.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .comment_object_type import CommentObjectType + + +class Comment(UniversalBaseModel): + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + object_type: typing_extensions.Annotated[ + CommentObjectType, FieldMetadata(alias="objectType") + ] + object_id: typing_extensions.Annotated[str, FieldMetadata(alias="objectId")] + content: str + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = pydantic.Field(default=None) + """ + The user ID of the comment author + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/comment_object_type.py b/langfuse/api/commons/types/comment_object_type.py similarity index 92% rename from langfuse/api/resources/commons/types/comment_object_type.py rename to langfuse/api/commons/types/comment_object_type.py index 9c6c134c6..fbd0d4e49 100644 --- a/langfuse/api/resources/commons/types/comment_object_type.py +++ b/langfuse/api/commons/types/comment_object_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class CommentObjectType(str, enum.Enum): +class CommentObjectType(enum.StrEnum): TRACE = "TRACE" OBSERVATION = "OBSERVATION" SESSION = "SESSION" diff --git a/langfuse/api/commons/types/config_category.py b/langfuse/api/commons/types/config_category.py new file mode 100644 index 000000000..4ca546e35 --- /dev/null +++ b/langfuse/api/commons/types/config_category.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ConfigCategory(UniversalBaseModel): + value: float + label: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/correction_score.py b/langfuse/api/commons/types/correction_score.py new file mode 100644 index 000000000..9b37071f4 --- /dev/null +++ b/langfuse/api/commons/types/correction_score.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score import BaseScore + + +class CorrectionScore(BaseScore): + value: float = pydantic.Field() + """ + The numeric value of the score. Always 0 for correction scores. + """ + + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The string representation of the correction content + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/create_score_value.py b/langfuse/api/commons/types/create_score_value.py similarity index 100% rename from langfuse/api/resources/commons/types/create_score_value.py rename to langfuse/api/commons/types/create_score_value.py diff --git a/langfuse/api/commons/types/dataset.py b/langfuse/api/commons/types/dataset.py new file mode 100644 index 000000000..d312b291a --- /dev/null +++ b/langfuse/api/commons/types/dataset.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class Dataset(UniversalBaseModel): + id: str + name: str + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the dataset + """ + + metadata: typing.Any = pydantic.Field() + """ + Metadata associated with the dataset + """ + + input_schema: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="inputSchema") + ] = pydantic.Field(default=None) + """ + JSON Schema for validating dataset item inputs + """ + + expected_output_schema: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="expectedOutputSchema") + ] = pydantic.Field(default=None) + """ + JSON Schema for validating dataset item expected outputs + """ + + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/dataset_item.py b/langfuse/api/commons/types/dataset_item.py new file mode 100644 index 000000000..54a13d81a --- /dev/null +++ b/langfuse/api/commons/types/dataset_item.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .dataset_status import DatasetStatus + + +class DatasetItem(UniversalBaseModel): + id: str + status: DatasetStatus + input: typing.Any = pydantic.Field() + """ + Input data for the dataset item + """ + + expected_output: typing_extensions.Annotated[ + typing.Any, FieldMetadata(alias="expectedOutput") + ] = pydantic.Field() + """ + Expected output for the dataset item + """ + + metadata: typing.Any = pydantic.Field() + """ + Metadata associated with the dataset item + """ + + source_trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sourceTraceId") + ] = pydantic.Field(default=None) + """ + The trace ID that sourced this dataset item + """ + + source_observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sourceObservationId") + ] = pydantic.Field(default=None) + """ + The observation ID that sourced this dataset item + """ + + dataset_id: typing_extensions.Annotated[str, FieldMetadata(alias="datasetId")] + dataset_name: typing_extensions.Annotated[str, FieldMetadata(alias="datasetName")] + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/dataset_run.py b/langfuse/api/commons/types/dataset_run.py new file mode 100644 index 000000000..0b5d8566c --- /dev/null +++ b/langfuse/api/commons/types/dataset_run.py @@ -0,0 +1,63 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class DatasetRun(UniversalBaseModel): + id: str = pydantic.Field() + """ + Unique identifier of the dataset run + """ + + name: str = pydantic.Field() + """ + Name of the dataset run + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the run + """ + + metadata: typing.Any = pydantic.Field() + """ + Metadata of the dataset run + """ + + dataset_id: typing_extensions.Annotated[str, FieldMetadata(alias="datasetId")] = ( + pydantic.Field() + ) + """ + Id of the associated dataset + """ + + dataset_name: typing_extensions.Annotated[ + str, FieldMetadata(alias="datasetName") + ] = pydantic.Field() + """ + Name of the associated dataset + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] = pydantic.Field() + """ + The date and time when the dataset run was created + """ + + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] = pydantic.Field() + """ + The date and time when the dataset run was last updated + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/dataset_run_item.py b/langfuse/api/commons/types/dataset_run_item.py new file mode 100644 index 000000000..1ee364ffa --- /dev/null +++ b/langfuse/api/commons/types/dataset_run_item.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class DatasetRunItem(UniversalBaseModel): + id: str + dataset_run_id: typing_extensions.Annotated[ + str, FieldMetadata(alias="datasetRunId") + ] + dataset_run_name: typing_extensions.Annotated[ + str, FieldMetadata(alias="datasetRunName") + ] + dataset_item_id: typing_extensions.Annotated[ + str, FieldMetadata(alias="datasetItemId") + ] + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = pydantic.Field(default=None) + """ + The observation ID associated with this run item + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/dataset_run_with_items.py b/langfuse/api/commons/types/dataset_run_with_items.py new file mode 100644 index 000000000..b5995dd30 --- /dev/null +++ b/langfuse/api/commons/types/dataset_run_with_items.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .dataset_run import DatasetRun +from .dataset_run_item import DatasetRunItem + + +class DatasetRunWithItems(DatasetRun): + dataset_run_items: typing_extensions.Annotated[ + typing.List[DatasetRunItem], FieldMetadata(alias="datasetRunItems") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/dataset_status.py b/langfuse/api/commons/types/dataset_status.py similarity index 88% rename from langfuse/api/resources/commons/types/dataset_status.py rename to langfuse/api/commons/types/dataset_status.py index 09eac62fe..f8e681aeb 100644 --- a/langfuse/api/resources/commons/types/dataset_status.py +++ b/langfuse/api/commons/types/dataset_status.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class DatasetStatus(str, enum.Enum): +class DatasetStatus(enum.StrEnum): ACTIVE = "ACTIVE" ARCHIVED = "ARCHIVED" diff --git a/langfuse/api/resources/commons/types/map_value.py b/langfuse/api/commons/types/map_value.py similarity index 88% rename from langfuse/api/resources/commons/types/map_value.py rename to langfuse/api/commons/types/map_value.py index e1e771a9b..46115a967 100644 --- a/langfuse/api/resources/commons/types/map_value.py +++ b/langfuse/api/commons/types/map_value.py @@ -5,6 +5,7 @@ MapValue = typing.Union[ typing.Optional[str], typing.Optional[int], + typing.Optional[float], typing.Optional[bool], typing.Optional[typing.List[str]], ] diff --git a/langfuse/api/resources/commons/types/model.py b/langfuse/api/commons/types/model.py similarity index 55% rename from langfuse/api/resources/commons/types/model.py rename to langfuse/api/commons/types/model.py index 86fce3c2d..e09313e8a 100644 --- a/langfuse/api/resources/commons/types/model.py +++ b/langfuse/api/commons/types/model.py @@ -3,14 +3,16 @@ import datetime as dt import typing -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata from .model_price import ModelPrice from .model_usage_unit import ModelUsageUnit from .pricing_tier import PricingTier -class Model(pydantic_v1.BaseModel): +class Model(UniversalBaseModel): """ Model definition used for transforming usage into USD cost and/or tokenization. @@ -24,68 +26,78 @@ class Model(pydantic_v1.BaseModel): """ id: str - model_name: str = pydantic_v1.Field(alias="modelName") + model_name: typing_extensions.Annotated[str, FieldMetadata(alias="modelName")] = ( + pydantic.Field() + ) """ Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/model_price.py b/langfuse/api/commons/types/model_price.py new file mode 100644 index 000000000..a2e2288fa --- /dev/null +++ b/langfuse/api/commons/types/model_price.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ModelPrice(UniversalBaseModel): + price: float + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/model_usage_unit.py b/langfuse/api/commons/types/model_usage_unit.py similarity index 94% rename from langfuse/api/resources/commons/types/model_usage_unit.py rename to langfuse/api/commons/types/model_usage_unit.py index 35253f92e..281223960 100644 --- a/langfuse/api/resources/commons/types/model_usage_unit.py +++ b/langfuse/api/commons/types/model_usage_unit.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class ModelUsageUnit(str, enum.Enum): +class ModelUsageUnit(enum.StrEnum): """ Unit of usage in Langfuse """ diff --git a/langfuse/api/commons/types/numeric_score.py b/langfuse/api/commons/types/numeric_score.py new file mode 100644 index 000000000..63ced82ab --- /dev/null +++ b/langfuse/api/commons/types/numeric_score.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score import BaseScore + + +class NumericScore(BaseScore): + value: float = pydantic.Field() + """ + The numeric value of the score + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/numeric_score_v1.py b/langfuse/api/commons/types/numeric_score_v1.py new file mode 100644 index 000000000..872c89aca --- /dev/null +++ b/langfuse/api/commons/types/numeric_score_v1.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score_v1 import BaseScoreV1 + + +class NumericScoreV1(BaseScoreV1): + value: float = pydantic.Field() + """ + The numeric value of the score + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/observation.py b/langfuse/api/commons/types/observation.py new file mode 100644 index 000000000..91bd50643 --- /dev/null +++ b/langfuse/api/commons/types/observation.py @@ -0,0 +1,142 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .observation_level import ObservationLevel +from .usage import Usage + + +class Observation(UniversalBaseModel): + id: str = pydantic.Field() + """ + The unique identifier of the observation + """ + + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = pydantic.Field(default=None) + """ + The trace ID associated with the observation + """ + + type: str = pydantic.Field() + """ + The type of the observation + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + The name of the observation + """ + + start_time: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="startTime") + ] = pydantic.Field() + """ + The start time of the observation + """ + + end_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="endTime") + ] = pydantic.Field(default=None) + """ + The end time of the observation. + """ + + completion_start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completionStartTime") + ] = pydantic.Field(default=None) + """ + The completion start time of the observation + """ + + model: typing.Optional[str] = pydantic.Field(default=None) + """ + The model used for the observation + """ + + model_parameters: typing_extensions.Annotated[ + typing.Any, FieldMetadata(alias="modelParameters") + ] = pydantic.Field() + """ + The parameters of the model used for the observation + """ + + input: typing.Any = pydantic.Field() + """ + The input data of the observation + """ + + version: typing.Optional[str] = pydantic.Field(default=None) + """ + The version of the observation + """ + + metadata: typing.Any = pydantic.Field() + """ + Additional metadata of the observation + """ + + output: typing.Any = pydantic.Field() + """ + The output data of the observation + """ + + usage: Usage = pydantic.Field() + """ + (Deprecated. Use usageDetails and costDetails instead.) The usage data of the observation + """ + + level: ObservationLevel = pydantic.Field() + """ + The level of the observation + """ + + status_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="statusMessage") + ] = pydantic.Field(default=None) + """ + The status message of the observation + """ + + parent_observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="parentObservationId") + ] = pydantic.Field(default=None) + """ + The parent observation ID + """ + + prompt_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="promptId") + ] = pydantic.Field(default=None) + """ + The prompt ID associated with the observation + """ + + usage_details: typing_extensions.Annotated[ + typing.Dict[str, int], FieldMetadata(alias="usageDetails") + ] = pydantic.Field() + """ + The usage details of the observation. Key is the name of the usage metric, value is the number of units consumed. The total key is the sum of all (non-total) usage metrics or the total value ingested. + """ + + cost_details: typing_extensions.Annotated[ + typing.Dict[str, float], FieldMetadata(alias="costDetails") + ] = pydantic.Field() + """ + The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested. + """ + + environment: str = pydantic.Field() + """ + The environment from which this observation originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/observation_level.py b/langfuse/api/commons/types/observation_level.py similarity index 91% rename from langfuse/api/resources/commons/types/observation_level.py rename to langfuse/api/commons/types/observation_level.py index c33e87b59..8629ef31d 100644 --- a/langfuse/api/resources/commons/types/observation_level.py +++ b/langfuse/api/commons/types/observation_level.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class ObservationLevel(str, enum.Enum): +class ObservationLevel(enum.StrEnum): DEBUG = "DEBUG" DEFAULT = "DEFAULT" WARNING = "WARNING" diff --git a/langfuse/api/commons/types/observations_view.py b/langfuse/api/commons/types/observations_view.py new file mode 100644 index 000000000..fc97c27e7 --- /dev/null +++ b/langfuse/api/commons/types/observations_view.py @@ -0,0 +1,89 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .observation import Observation + + +class ObservationsView(Observation): + prompt_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="promptName") + ] = pydantic.Field(default=None) + """ + The name of the prompt associated with the observation + """ + + prompt_version: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="promptVersion") + ] = pydantic.Field(default=None) + """ + The version of the prompt associated with the observation + """ + + model_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="modelId") + ] = pydantic.Field(default=None) + """ + The unique identifier of the model + """ + + input_price: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="inputPrice") + ] = pydantic.Field(default=None) + """ + The price of the input in USD + """ + + output_price: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="outputPrice") + ] = pydantic.Field(default=None) + """ + The price of the output in USD. + """ + + total_price: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="totalPrice") + ] = pydantic.Field(default=None) + """ + The total price in USD. + """ + + calculated_input_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="calculatedInputCost") + ] = pydantic.Field(default=None) + """ + (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the input in USD + """ + + calculated_output_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="calculatedOutputCost") + ] = pydantic.Field(default=None) + """ + (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the output in USD + """ + + calculated_total_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="calculatedTotalCost") + ] = pydantic.Field(default=None) + """ + (Deprecated. Use usageDetails and costDetails instead.) The calculated total cost in USD + """ + + latency: typing.Optional[float] = pydantic.Field(default=None) + """ + The latency in seconds. + """ + + time_to_first_token: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="timeToFirstToken") + ] = pydantic.Field(default=None) + """ + The time to the first token in seconds + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/pricing_tier.py b/langfuse/api/commons/types/pricing_tier.py similarity index 66% rename from langfuse/api/resources/commons/types/pricing_tier.py rename to langfuse/api/commons/types/pricing_tier.py index 031d142c0..49b99096b 100644 --- a/langfuse/api/resources/commons/types/pricing_tier.py +++ b/langfuse/api/commons/types/pricing_tier.py @@ -1,14 +1,15 @@ # This file was auto-generated by Fern from our API Definition. -import datetime as dt import typing -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata from .pricing_tier_condition import PricingTierCondition -class PricingTier(pydantic_v1.BaseModel): +class PricingTier(UniversalBaseModel): """ Pricing tier definition with conditional pricing based on usage thresholds. @@ -29,19 +30,21 @@ class PricingTier(pydantic_v1.BaseModel): Every model must have exactly one default tier to ensure cost calculation always succeeds. """ - id: str = pydantic_v1.Field() + id: str = pydantic.Field() """ Unique identifier for the pricing tier """ - name: str = pydantic_v1.Field() + name: str = pydantic.Field() """ Name of the pricing tier for display and identification purposes. Examples: "Standard", "High Volume Tier", "Large Context", "Extended Context Tier" """ - is_default: bool = pydantic_v1.Field(alias="isDefault") + is_default: typing_extensions.Annotated[bool, FieldMetadata(alias="isDefault")] = ( + pydantic.Field() + ) """ Whether this is the default tier. Every model must have exactly one default tier with priority 0 and no conditions. @@ -49,7 +52,7 @@ class PricingTier(pydantic_v1.BaseModel): It typically represents the base pricing for standard usage patterns. """ - priority: int = pydantic_v1.Field() + priority: int = pydantic.Field() """ Priority for tier matching evaluation. Lower numbers = higher priority (evaluated first). @@ -63,7 +66,7 @@ class PricingTier(pydantic_v1.BaseModel): This ensures more specific conditions are checked before general ones. """ - conditions: typing.List[PricingTierCondition] = pydantic_v1.Field() + conditions: typing.List[PricingTierCondition] = pydantic.Field() """ Array of conditions that must ALL be met for this tier to match (AND logic). @@ -73,7 +76,7 @@ class PricingTier(pydantic_v1.BaseModel): Multiple conditions enable complex matching scenarios (e.g., "high input tokens AND low output tokens"). """ - prices: typing.Dict[str, float] = pydantic_v1.Field() + prices: typing.Dict[str, float] = pydantic.Field() """ Prices (USD) by usage type for this tier. @@ -83,35 +86,6 @@ class PricingTier(pydantic_v1.BaseModel): Example: {"input": 0.000003, "output": 0.000015} means $3 per million input tokens and $15 per million output tokens. """ - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/pricing_tier_condition.py b/langfuse/api/commons/types/pricing_tier_condition.py similarity index 57% rename from langfuse/api/resources/commons/types/pricing_tier_condition.py rename to langfuse/api/commons/types/pricing_tier_condition.py index 8b89fe116..9ebbba0af 100644 --- a/langfuse/api/resources/commons/types/pricing_tier_condition.py +++ b/langfuse/api/commons/types/pricing_tier_condition.py @@ -1,14 +1,15 @@ # This file was auto-generated by Fern from our API Definition. -import datetime as dt import typing -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata from .pricing_tier_operator import PricingTierOperator -class PricingTierCondition(pydantic_v1.BaseModel): +class PricingTierCondition(UniversalBaseModel): """ Condition for matching a pricing tier based on usage details. Used to implement tiered pricing models where costs vary based on usage thresholds. @@ -24,7 +25,9 @@ class PricingTierCondition(pydantic_v1.BaseModel): - Volume-based pricing: Different rates based on total request or token count """ - usage_detail_pattern: str = pydantic_v1.Field(alias="usageDetailPattern") + usage_detail_pattern: typing_extensions.Annotated[ + str, FieldMetadata(alias="usageDetailPattern") + ] = pydantic.Field() """ Regex pattern to match against usage detail keys. All matching keys' values are summed for threshold comparison. @@ -36,7 +39,7 @@ class PricingTierCondition(pydantic_v1.BaseModel): The pattern is case-insensitive by default. If no keys match, the sum is treated as zero. """ - operator: PricingTierOperator = pydantic_v1.Field() + operator: PricingTierOperator = pydantic.Field() """ Comparison operator to apply between the summed value and the threshold. @@ -48,45 +51,18 @@ class PricingTierCondition(pydantic_v1.BaseModel): - neq: not equal (sum != threshold) """ - value: float = pydantic_v1.Field() + value: float = pydantic.Field() """ Threshold value for comparison. For token-based pricing, this is typically the token count threshold (e.g., 200000 for a 200K token threshold). """ - case_sensitive: bool = pydantic_v1.Field(alias="caseSensitive") + case_sensitive: typing_extensions.Annotated[ + bool, FieldMetadata(alias="caseSensitive") + ] = pydantic.Field() """ Whether the regex pattern matching is case-sensitive. Default is false (case-insensitive matching). """ - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/pricing_tier_input.py b/langfuse/api/commons/types/pricing_tier_input.py similarity index 59% rename from langfuse/api/resources/commons/types/pricing_tier_input.py rename to langfuse/api/commons/types/pricing_tier_input.py index 00dfdb37b..c25d7f716 100644 --- a/langfuse/api/resources/commons/types/pricing_tier_input.py +++ b/langfuse/api/commons/types/pricing_tier_input.py @@ -1,14 +1,15 @@ # This file was auto-generated by Fern from our API Definition. -import datetime as dt import typing -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata from .pricing_tier_condition import PricingTierCondition -class PricingTierInput(pydantic_v1.BaseModel): +class PricingTierInput(UniversalBaseModel): """ Input schema for creating a pricing tier. The tier ID will be automatically generated server-side. @@ -21,14 +22,16 @@ class PricingTierInput(pydantic_v1.BaseModel): See PricingTier for detailed information about how tiers work and why they're useful. """ - name: str = pydantic_v1.Field() + name: str = pydantic.Field() """ Name of the pricing tier for display and identification purposes. Must be unique within the model. Common patterns: "Standard", "High Volume Tier", "Extended Context" """ - is_default: bool = pydantic_v1.Field(alias="isDefault") + is_default: typing_extensions.Annotated[bool, FieldMetadata(alias="isDefault")] = ( + pydantic.Field() + ) """ Whether this is the default tier. Exactly one tier per model must be marked as default. @@ -40,7 +43,7 @@ class PricingTierInput(pydantic_v1.BaseModel): The default tier acts as a fallback when no conditional tiers match. """ - priority: int = pydantic_v1.Field() + priority: int = pydantic.Field() """ Priority for tier matching evaluation. Lower numbers = higher priority (evaluated first). @@ -48,7 +51,7 @@ class PricingTierInput(pydantic_v1.BaseModel): Conditional tiers should use priority 1, 2, 3, etc. based on their specificity. """ - conditions: typing.List[PricingTierCondition] = pydantic_v1.Field() + conditions: typing.List[PricingTierCondition] = pydantic.Field() """ Array of conditions that must ALL be met for this tier to match (AND logic). @@ -58,7 +61,7 @@ class PricingTierInput(pydantic_v1.BaseModel): Each condition specifies a regex pattern, operator, and threshold value for matching against usage details. """ - prices: typing.Dict[str, float] = pydantic_v1.Field() + prices: typing.Dict[str, float] = pydantic.Field() """ Prices (USD) by usage type for this tier. At least one price must be defined. @@ -68,35 +71,6 @@ class PricingTierInput(pydantic_v1.BaseModel): Example: {"input": 0.000003, "output": 0.000015} represents $3 per million input tokens and $15 per million output tokens. """ - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/pricing_tier_operator.py b/langfuse/api/commons/types/pricing_tier_operator.py similarity index 93% rename from langfuse/api/resources/commons/types/pricing_tier_operator.py rename to langfuse/api/commons/types/pricing_tier_operator.py index c5af10199..6d40799ec 100644 --- a/langfuse/api/resources/commons/types/pricing_tier_operator.py +++ b/langfuse/api/commons/types/pricing_tier_operator.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class PricingTierOperator(str, enum.Enum): +class PricingTierOperator(enum.StrEnum): """ Comparison operators for pricing tier conditions """ diff --git a/langfuse/api/commons/types/score.py b/langfuse/api/commons/types/score.py new file mode 100644 index 000000000..bfcb94f3d --- /dev/null +++ b/langfuse/api/commons/types/score.py @@ -0,0 +1,201 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .score_source import ScoreSource + + +class Score_Numeric(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["NUMERIC"], FieldMetadata(alias="dataType") + ] = "NUMERIC" + value: float + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class Score_Categorical(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CATEGORICAL"], FieldMetadata(alias="dataType") + ] = "CATEGORICAL" + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class Score_Boolean(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["BOOLEAN"], FieldMetadata(alias="dataType") + ] = "BOOLEAN" + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class Score_Correction(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CORRECTION"], FieldMetadata(alias="dataType") + ] = "CORRECTION" + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +Score = typing_extensions.Annotated[ + typing.Union[Score_Numeric, Score_Categorical, Score_Boolean, Score_Correction], + pydantic.Field(discriminator="data_type"), +] diff --git a/langfuse/api/commons/types/score_config.py b/langfuse/api/commons/types/score_config.py new file mode 100644 index 000000000..36e45e168 --- /dev/null +++ b/langfuse/api/commons/types/score_config.py @@ -0,0 +1,66 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .config_category import ConfigCategory +from .score_config_data_type import ScoreConfigDataType + + +class ScoreConfig(UniversalBaseModel): + """ + Configuration for a score + """ + + id: str + name: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + data_type: typing_extensions.Annotated[ + ScoreConfigDataType, FieldMetadata(alias="dataType") + ] + is_archived: typing_extensions.Annotated[ + bool, FieldMetadata(alias="isArchived") + ] = pydantic.Field() + """ + Whether the score config is archived. Defaults to false + """ + + min_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="minValue") + ] = pydantic.Field(default=None) + """ + Sets minimum value for numerical scores. If not set, the minimum value defaults to -∞ + """ + + max_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="maxValue") + ] = pydantic.Field(default=None) + """ + Sets maximum value for numerical scores. If not set, the maximum value defaults to +∞ + """ + + categories: typing.Optional[typing.List[ConfigCategory]] = pydantic.Field( + default=None + ) + """ + Configures custom categories for categorical scores + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the score config + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/commons/types/score_config_data_type.py b/langfuse/api/commons/types/score_config_data_type.py similarity index 90% rename from langfuse/api/resources/commons/types/score_config_data_type.py rename to langfuse/api/commons/types/score_config_data_type.py index a7c9e7251..4f4660305 100644 --- a/langfuse/api/resources/commons/types/score_config_data_type.py +++ b/langfuse/api/commons/types/score_config_data_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class ScoreConfigDataType(str, enum.Enum): +class ScoreConfigDataType(enum.StrEnum): NUMERIC = "NUMERIC" BOOLEAN = "BOOLEAN" CATEGORICAL = "CATEGORICAL" diff --git a/langfuse/api/resources/commons/types/score_data_type.py b/langfuse/api/commons/types/score_data_type.py similarity index 92% rename from langfuse/api/resources/commons/types/score_data_type.py rename to langfuse/api/commons/types/score_data_type.py index 67bd9958b..c29a77f07 100644 --- a/langfuse/api/resources/commons/types/score_data_type.py +++ b/langfuse/api/commons/types/score_data_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class ScoreDataType(str, enum.Enum): +class ScoreDataType(enum.StrEnum): NUMERIC = "NUMERIC" BOOLEAN = "BOOLEAN" CATEGORICAL = "CATEGORICAL" diff --git a/langfuse/api/resources/commons/types/score_source.py b/langfuse/api/commons/types/score_source.py similarity index 90% rename from langfuse/api/resources/commons/types/score_source.py rename to langfuse/api/commons/types/score_source.py index 699f078b7..ee1398a9a 100644 --- a/langfuse/api/resources/commons/types/score_source.py +++ b/langfuse/api/commons/types/score_source.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class ScoreSource(str, enum.Enum): +class ScoreSource(enum.StrEnum): ANNOTATION = "ANNOTATION" API = "API" EVAL = "EVAL" diff --git a/langfuse/api/commons/types/score_v1.py b/langfuse/api/commons/types/score_v1.py new file mode 100644 index 000000000..1a409ce2f --- /dev/null +++ b/langfuse/api/commons/types/score_v1.py @@ -0,0 +1,131 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .score_source import ScoreSource + + +class ScoreV1_Numeric(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["NUMERIC"], FieldMetadata(alias="dataType") + ] = "NUMERIC" + value: float + id: str + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + name: str + source: ScoreSource + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreV1_Categorical(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CATEGORICAL"], FieldMetadata(alias="dataType") + ] = "CATEGORICAL" + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + name: str + source: ScoreSource + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreV1_Boolean(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["BOOLEAN"], FieldMetadata(alias="dataType") + ] = "BOOLEAN" + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + name: str + source: ScoreSource + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +ScoreV1 = typing_extensions.Annotated[ + typing.Union[ScoreV1_Numeric, ScoreV1_Categorical, ScoreV1_Boolean], + pydantic.Field(discriminator="data_type"), +] diff --git a/langfuse/api/commons/types/session.py b/langfuse/api/commons/types/session.py new file mode 100644 index 000000000..c254b27cd --- /dev/null +++ b/langfuse/api/commons/types/session.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class Session(UniversalBaseModel): + id: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + environment: str = pydantic.Field() + """ + The environment from which this session originated. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/session_with_traces.py b/langfuse/api/commons/types/session_with_traces.py new file mode 100644 index 000000000..d04eef54e --- /dev/null +++ b/langfuse/api/commons/types/session_with_traces.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .session import Session +from .trace import Trace + + +class SessionWithTraces(Session): + traces: typing.List[Trace] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/trace.py b/langfuse/api/commons/types/trace.py new file mode 100644 index 000000000..6eafa9fcd --- /dev/null +++ b/langfuse/api/commons/types/trace.py @@ -0,0 +1,84 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class Trace(UniversalBaseModel): + id: str = pydantic.Field() + """ + The unique identifier of a trace + """ + + timestamp: dt.datetime = pydantic.Field() + """ + The timestamp when the trace was created + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + The name of the trace + """ + + input: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + The input data of the trace. Can be any JSON. + """ + + output: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + The output data of the trace. Can be any JSON. + """ + + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = pydantic.Field(default=None) + """ + The session identifier associated with the trace + """ + + release: typing.Optional[str] = pydantic.Field(default=None) + """ + The release version of the application when the trace was created + """ + + version: typing.Optional[str] = pydantic.Field(default=None) + """ + The version of the trace + """ + + user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="userId") + ] = pydantic.Field(default=None) + """ + The user identifier associated with the trace + """ + + metadata: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + The metadata associated with the trace. Can be any JSON. + """ + + tags: typing.List[str] = pydantic.Field() + """ + The tags associated with the trace. + """ + + public: bool = pydantic.Field() + """ + Public traces are accessible via url without login + """ + + environment: str = pydantic.Field() + """ + The environment from which this trace originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/trace_with_details.py b/langfuse/api/commons/types/trace_with_details.py new file mode 100644 index 000000000..958d76901 --- /dev/null +++ b/langfuse/api/commons/types/trace_with_details.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .trace import Trace + + +class TraceWithDetails(Trace): + html_path: typing_extensions.Annotated[str, FieldMetadata(alias="htmlPath")] = ( + pydantic.Field() + ) + """ + Path of trace in Langfuse UI + """ + + latency: typing.Optional[float] = pydantic.Field(default=None) + """ + Latency of trace in seconds + """ + + total_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="totalCost") + ] = pydantic.Field(default=None) + """ + Cost of trace in USD + """ + + observations: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of observation ids + """ + + scores: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of score ids + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/trace_with_full_details.py b/langfuse/api/commons/types/trace_with_full_details.py new file mode 100644 index 000000000..f14757ce5 --- /dev/null +++ b/langfuse/api/commons/types/trace_with_full_details.py @@ -0,0 +1,45 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .observations_view import ObservationsView +from .score_v1 import ScoreV1 +from .trace import Trace + + +class TraceWithFullDetails(Trace): + html_path: typing_extensions.Annotated[str, FieldMetadata(alias="htmlPath")] = ( + pydantic.Field() + ) + """ + Path of trace in Langfuse UI + """ + + latency: typing.Optional[float] = pydantic.Field(default=None) + """ + Latency of trace in seconds + """ + + total_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="totalCost") + ] = pydantic.Field(default=None) + """ + Cost of trace in USD + """ + + observations: typing.List[ObservationsView] = pydantic.Field() + """ + List of observations + """ + + scores: typing.List[ScoreV1] = pydantic.Field() + """ + List of scores + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/usage.py b/langfuse/api/commons/types/usage.py new file mode 100644 index 000000000..b3c1fd048 --- /dev/null +++ b/langfuse/api/commons/types/usage.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class Usage(UniversalBaseModel): + """ + (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost + """ + + input: int = pydantic.Field() + """ + Number of input units (e.g. tokens) + """ + + output: int = pydantic.Field() + """ + Number of output units (e.g. tokens) + """ + + total: int = pydantic.Field() + """ + Defaults to input+output if not set + """ + + unit: typing.Optional[str] = pydantic.Field(default=None) + """ + Unit of measurement + """ + + input_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="inputCost") + ] = pydantic.Field(default=None) + """ + USD input cost + """ + + output_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="outputCost") + ] = pydantic.Field(default=None) + """ + USD output cost + """ + + total_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="totalCost") + ] = pydantic.Field(default=None) + """ + USD total cost, defaults to input+output + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/core/__init__.py b/langfuse/api/core/__init__.py index 58ad52ad2..91742cd87 100644 --- a/langfuse/api/core/__init__.py +++ b/langfuse/api/core/__init__.py @@ -1,30 +1,111 @@ # This file was auto-generated by Fern from our API Definition. -from .api_error import ApiError -from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper -from .datetime_utils import serialize_datetime -from .file import File, convert_file_dict_to_httpx_tuples -from .http_client import AsyncHttpClient, HttpClient -from .jsonable_encoder import jsonable_encoder -from .pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .query_encoder import encode_query -from .remove_none_from_dict import remove_none_from_dict -from .request_options import RequestOptions +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .api_error import ApiError + from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper + from .datetime_utils import serialize_datetime + from .file import File, convert_file_dict_to_httpx_tuples, with_content_type + from .http_client import AsyncHttpClient, HttpClient + from .http_response import AsyncHttpResponse, HttpResponse + from .jsonable_encoder import jsonable_encoder + from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, + ) + from .query_encoder import encode_query + from .remove_none_from_dict import remove_none_from_dict + from .request_options import RequestOptions + from .serialization import FieldMetadata, convert_and_respect_annotation_metadata +_dynamic_imports: typing.Dict[str, str] = { + "ApiError": ".api_error", + "AsyncClientWrapper": ".client_wrapper", + "AsyncHttpClient": ".http_client", + "AsyncHttpResponse": ".http_response", + "BaseClientWrapper": ".client_wrapper", + "FieldMetadata": ".serialization", + "File": ".file", + "HttpClient": ".http_client", + "HttpResponse": ".http_response", + "IS_PYDANTIC_V2": ".pydantic_utilities", + "RequestOptions": ".request_options", + "SyncClientWrapper": ".client_wrapper", + "UniversalBaseModel": ".pydantic_utilities", + "UniversalRootModel": ".pydantic_utilities", + "convert_and_respect_annotation_metadata": ".serialization", + "convert_file_dict_to_httpx_tuples": ".file", + "encode_query": ".query_encoder", + "jsonable_encoder": ".jsonable_encoder", + "parse_obj_as": ".pydantic_utilities", + "remove_none_from_dict": ".remove_none_from_dict", + "serialize_datetime": ".datetime_utils", + "universal_field_validator": ".pydantic_utilities", + "universal_root_validator": ".pydantic_utilities", + "update_forward_refs": ".pydantic_utilities", + "with_content_type": ".file", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + __all__ = [ "ApiError", "AsyncClientWrapper", "AsyncHttpClient", + "AsyncHttpResponse", "BaseClientWrapper", + "FieldMetadata", "File", "HttpClient", + "HttpResponse", + "IS_PYDANTIC_V2", "RequestOptions", "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", "convert_file_dict_to_httpx_tuples", - "deep_union_pydantic_dicts", "encode_query", "jsonable_encoder", - "pydantic_v1", + "parse_obj_as", "remove_none_from_dict", "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", + "with_content_type", ] diff --git a/langfuse/api/core/api_error.py b/langfuse/api/core/api_error.py index da734b580..6f850a60c 100644 --- a/langfuse/api/core/api_error.py +++ b/langfuse/api/core/api_error.py @@ -1,17 +1,23 @@ # This file was auto-generated by Fern from our API Definition. -import typing +from typing import Any, Dict, Optional class ApiError(Exception): - status_code: typing.Optional[int] - body: typing.Any + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any def __init__( - self, *, status_code: typing.Optional[int] = None, body: typing.Any = None - ): + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = headers self.status_code = status_code self.body = body def __str__(self) -> str: - return f"status_code: {self.status_code}, body: {self.body}" + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/langfuse/api/core/client_wrapper.py b/langfuse/api/core/client_wrapper.py index 8a053f4a7..22bb6a70b 100644 --- a/langfuse/api/core/client_wrapper.py +++ b/langfuse/api/core/client_wrapper.py @@ -3,7 +3,6 @@ import typing import httpx - from .http_client import AsyncHttpClient, HttpClient @@ -16,6 +15,7 @@ def __init__( x_langfuse_public_key: typing.Optional[str] = None, username: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, ): @@ -24,11 +24,15 @@ def __init__( self._x_langfuse_public_key = x_langfuse_public_key self._username = username self._password = password + self._headers = headers self._base_url = base_url self._timeout = timeout def get_headers(self) -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {"X-Fern-Language": "Python"} + headers: typing.Dict[str, str] = { + "X-Fern-Language": "Python", + **(self.get_custom_headers() or {}), + } username = self._get_username() password = self._get_password() if username is not None and password is not None: @@ -53,6 +57,9 @@ def _get_password(self) -> typing.Optional[str]: else: return self._password() + def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: + return self._headers + def get_base_url(self) -> str: return self._base_url @@ -69,6 +76,7 @@ def __init__( x_langfuse_public_key: typing.Optional[str] = None, username: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.Client, @@ -79,14 +87,15 @@ def __init__( x_langfuse_public_key=x_langfuse_public_key, username=username, password=password, + headers=headers, base_url=base_url, timeout=timeout, ) self.httpx_client = HttpClient( httpx_client=httpx_client, - base_headers=self.get_headers(), - base_timeout=self.get_timeout(), - base_url=self.get_base_url(), + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, ) @@ -99,8 +108,10 @@ def __init__( x_langfuse_public_key: typing.Optional[str] = None, username: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, password: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, httpx_client: httpx.AsyncClient, ): super().__init__( @@ -109,12 +120,22 @@ def __init__( x_langfuse_public_key=x_langfuse_public_key, username=username, password=password, + headers=headers, base_url=base_url, timeout=timeout, ) + self._async_token = async_token self.httpx_client = AsyncHttpClient( httpx_client=httpx_client, - base_headers=self.get_headers(), - base_timeout=self.get_timeout(), - base_url=self.get_base_url(), + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + async_base_headers=self.async_get_headers, ) + + async def async_get_headers(self) -> typing.Dict[str, str]: + headers = self.get_headers() + if self._async_token is not None: + token = await self._async_token() + headers["Authorization"] = f"Bearer {token}" + return headers diff --git a/langfuse/api/core/enum.py b/langfuse/api/core/enum.py new file mode 100644 index 000000000..a3d17a67b --- /dev/null +++ b/langfuse/api/core/enum.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +Provides a StrEnum base class that works across Python versions. + +For Python >= 3.11, this re-exports the standard library enum.StrEnum. +For older Python versions, this defines a compatible StrEnum using the +(str, Enum) mixin pattern so that generated SDKs can use a single base +class in all supported Python versions. +""" + +import enum +import sys + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + + class StrEnum(str, enum.Enum): + pass diff --git a/langfuse/api/core/file.py b/langfuse/api/core/file.py index 6e0f92bfc..3467175cb 100644 --- a/langfuse/api/core/file.py +++ b/langfuse/api/core/file.py @@ -1,30 +1,30 @@ # This file was auto-generated by Fern from our API Definition. -import typing +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast # File typing inspired by the flexibility of types within the httpx library # https://github.com/encode/httpx/blob/master/httpx/_types.py -FileContent = typing.Union[typing.IO[bytes], bytes, str] -File = typing.Union[ +FileContent = Union[IO[bytes], bytes, str] +File = Union[ # file (or bytes) FileContent, # (filename, file (or bytes)) - typing.Tuple[typing.Optional[str], FileContent], + Tuple[Optional[str], FileContent], # (filename, file (or bytes), content_type) - typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]], + Tuple[Optional[str], FileContent, Optional[str]], # (filename, file (or bytes), content_type, headers) - typing.Tuple[ - typing.Optional[str], + Tuple[ + Optional[str], FileContent, - typing.Optional[str], - typing.Mapping[str, str], + Optional[str], + Mapping[str, str], ], ] def convert_file_dict_to_httpx_tuples( - d: typing.Dict[str, typing.Union[File, typing.List[File]]], -) -> typing.List[typing.Tuple[str, File]]: + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: """ The format we use is a list of tuples, where the first element is the name of the file and the second is the file object. Typically HTTPX wants @@ -41,3 +41,30 @@ def convert_file_dict_to_httpx_tuples( else: httpx_tuples.append((key, file_like)) return httpx_tuples + + +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, default_content_type) + elif len(file) == 3: + filename, content, file_content_type = cast( + Tuple[Optional[str], FileContent, Optional[str]], file + ) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) + elif len(file) == 4: + filename, content, file_content_type, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], + file, + ) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, default_content_type) diff --git a/langfuse/api/core/force_multipart.py b/langfuse/api/core/force_multipart.py new file mode 100644 index 000000000..5440913fd --- /dev/null +++ b/langfuse/api/core/force_multipart.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict + + +class ForceMultipartDict(Dict[str, Any]): + """ + A dictionary subclass that always evaluates to True in boolean contexts. + + This is used to force multipart/form-data encoding in HTTP requests even when + the dictionary is empty, which would normally evaluate to False. + """ + + def __bool__(self) -> bool: + return True + + +FORCE_MULTIPART = ForceMultipartDict() diff --git a/langfuse/api/core/http_client.py b/langfuse/api/core/http_client.py index 091f71bc1..3025a49ba 100644 --- a/langfuse/api/core/http_client.py +++ b/langfuse/api/core/http_client.py @@ -2,7 +2,6 @@ import asyncio import email.utils -import json import re import time import typing @@ -11,16 +10,17 @@ from random import random import httpx - from .file import File, convert_file_dict_to_httpx_tuples +from .force_multipart import FORCE_MULTIPART from .jsonable_encoder import jsonable_encoder from .query_encoder import encode_query -from .remove_none_from_dict import remove_none_from_dict +from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict from .request_options import RequestOptions +from httpx._types import RequestFiles -INITIAL_RETRY_DELAY_SECONDS = 0.5 -MAX_RETRY_DELAY_SECONDS = 10 -MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30 +INITIAL_RETRY_DELAY_SECONDS = 1.0 +MAX_RETRY_DELAY_SECONDS = 60.0 +JITTER_FACTOR = 0.2 # 20% random jitter def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: @@ -64,6 +64,38 @@ def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float return seconds +def _add_positive_jitter(delay: float) -> float: + """Add positive jitter (0-20%) to prevent thundering herd.""" + jitter_multiplier = 1 + random() * JITTER_FACTOR + return delay * jitter_multiplier + + +def _add_symmetric_jitter(delay: float) -> float: + """Add symmetric jitter (±10%) for exponential backoff.""" + jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR + return delay * jitter_multiplier + + +def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + Parse the X-RateLimit-Reset header (Unix timestamp in seconds). + Returns seconds to wait, or None if header is missing/invalid. + """ + reset_time_str = response_headers.get("x-ratelimit-reset") + if reset_time_str is None: + return None + + try: + reset_time = int(reset_time_str) + delay = reset_time - time.time() + if delay > 0: + return delay + except (ValueError, TypeError): + pass + + return None + + def _retry_timeout(response: httpx.Response, retries: int) -> float: """ Determine the amount of time to wait before retrying a request. @@ -71,24 +103,45 @@ def _retry_timeout(response: httpx.Response, retries: int) -> float: with a jitter to determine the number of seconds to wait. """ - # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + # 1. Check Retry-After header first retry_after = _parse_retry_after(response.headers) - if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER: - return retry_after + if retry_after is not None and retry_after > 0: + return min(retry_after, MAX_RETRY_DELAY_SECONDS) - # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS. - retry_delay = min( + # 2. Check X-RateLimit-Reset header (with positive jitter) + ratelimit_reset = _parse_x_ratelimit_reset(response.headers) + if ratelimit_reset is not None: + return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) + + # 3. Fall back to exponential backoff (with symmetric jitter) + backoff = min( INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS ) - - # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries. - timeout = retry_delay * (1 - 0.25 * random()) - return timeout if timeout >= 0 else 0 + return _add_symmetric_jitter(backoff) def _should_retry(response: httpx.Response) -> bool: - retriable_400s = [429, 408, 409] - return response.status_code >= 500 or response.status_code in retriable_400s + retryable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retryable_400s + + +def _maybe_filter_none_from_multipart_data( + data: typing.Optional[typing.Any], + request_files: typing.Optional[RequestFiles], + force_multipart: typing.Optional[bool], +) -> typing.Optional[typing.Any]: + """ + Filter None values from data body for multipart/form requests. + This prevents httpx from converting None to empty strings in multipart encoding. + Only applies when files are present or force_multipart is True. + """ + if ( + data is not None + and isinstance(data, typing.Mapping) + and (request_files or force_multipart) + ): + return remove_none_from_dict(data) + return data def remove_omit_from_dict( @@ -147,7 +200,10 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return ( + json_body if json_body != {} else None + ), data_body if data_body != {} else None class HttpClient: @@ -155,9 +211,9 @@ def __init__( self, *, httpx_client: httpx.Client, - base_timeout: typing.Optional[float], - base_headers: typing.Dict[str, str], - base_url: typing.Optional[str] = None, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, ): self.base_url = base_url self.base_timeout = base_timeout @@ -165,7 +221,10 @@ def __init__( self.httpx_client = httpx_client def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: - base_url = self.base_url if maybe_base_url is None else maybe_base_url + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + if base_url is None: raise ValueError( "A base_url is required to make this request, please provide one and try again." @@ -185,32 +244,74 @@ def request( typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]] ] = None, files: typing.Optional[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]] + typing.Union[ + typing.Dict[ + str, typing.Optional[typing.Union[File, typing.List[File]]] + ], + typing.List[typing.Tuple[str, File]], + ] ] = None, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, ) -> httpx.Response: base_url = self.get_base_url(base_url) timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout + else self.base_timeout() ) json_body, data_body = get_request_body( json=json, data=data, request_options=request_options, omit=omit ) + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples( + remove_omit_from_dict(remove_none_from_dict(files), omit) + ) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + data_body = _maybe_filter_none_from_multipart_data( + data_body, request_files, force_multipart + ) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + response = self.httpx_client.request( method=method, url=urllib.parse.urljoin(f"{base_url}/", path), headers=jsonable_encoder( remove_none_from_dict( { - **self.base_headers, + **self.base_headers(), **(headers if headers is not None else {}), **( request_options.get("additional_headers", {}) or {} @@ -220,40 +321,19 @@ def request( } ) ), - params=encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get( - "additional_query_parameters", {} - ) - or {} - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ), + params=_encoded_params if _encoded_params else None, json=json_body, data=data_body, content=content, - files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) - if files is not None - else None, + files=request_files, timeout=timeout, ) max_retries: int = ( - request_options.get("max_retries", 0) if request_options is not None else 0 + request_options.get("max_retries", 2) if request_options is not None else 2 ) if _should_retry(response=response): - if max_retries > retries: + if retries < max_retries: time.sleep(_retry_timeout(response=response, retries=retries)) return self.request( path=path, @@ -285,32 +365,73 @@ def stream( typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]] ] = None, files: typing.Optional[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]] + typing.Union[ + typing.Dict[ + str, typing.Optional[typing.Union[File, typing.List[File]]] + ], + typing.List[typing.Tuple[str, File]], + ] ] = None, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, ) -> typing.Iterator[httpx.Response]: base_url = self.get_base_url(base_url) timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout + else self.base_timeout() ) + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples( + remove_omit_from_dict(remove_none_from_dict(files), omit) + ) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + json_body, data_body = get_request_body( json=json, data=data, request_options=request_options, omit=omit ) + data_body = _maybe_filter_none_from_multipart_data( + data_body, request_files, force_multipart + ) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + with self.httpx_client.stream( method=method, url=urllib.parse.urljoin(f"{base_url}/", path), headers=jsonable_encoder( remove_none_from_dict( { - **self.base_headers, + **self.base_headers(), **(headers if headers is not None else {}), **( request_options.get("additional_headers", {}) @@ -320,31 +441,11 @@ def stream( } ) ), - params=encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get( - "additional_query_parameters", {} - ) - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ), + params=_encoded_params if _encoded_params else None, json=json_body, data=data_body, content=content, - files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) - if files is not None - else None, + files=request_files, timeout=timeout, ) as stream: yield stream @@ -355,17 +456,29 @@ def __init__( self, *, httpx_client: httpx.AsyncClient, - base_timeout: typing.Optional[float], - base_headers: typing.Dict[str, str], - base_url: typing.Optional[str] = None, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + async_base_headers: typing.Optional[ + typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]] + ] = None, ): self.base_url = base_url self.base_timeout = base_timeout self.base_headers = base_headers + self.async_base_headers = async_base_headers self.httpx_client = httpx_client + async def _get_headers(self) -> typing.Dict[str, str]: + if self.async_base_headers is not None: + return await self.async_base_headers() + return self.base_headers() + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: - base_url = self.base_url if maybe_base_url is None else maybe_base_url + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + if base_url is None: raise ValueError( "A base_url is required to make this request, please provide one and try again." @@ -385,25 +498,70 @@ async def request( typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]] ] = None, files: typing.Optional[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]] + typing.Union[ + typing.Dict[ + str, typing.Optional[typing.Union[File, typing.List[File]]] + ], + typing.List[typing.Tuple[str, File]], + ] ] = None, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, ) -> httpx.Response: base_url = self.get_base_url(base_url) timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout + else self.base_timeout() ) + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples( + remove_omit_from_dict(remove_none_from_dict(files), omit) + ) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + json_body, data_body = get_request_body( json=json, data=data, request_options=request_options, omit=omit ) + data_body = _maybe_filter_none_from_multipart_data( + data_body, request_files, force_multipart + ) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + # Add the input to each of these and do None-safety checks response = await self.httpx_client.request( method=method, @@ -411,7 +569,7 @@ async def request( headers=jsonable_encoder( remove_none_from_dict( { - **self.base_headers, + **_headers, **(headers if headers is not None else {}), **( request_options.get("additional_headers", {}) or {} @@ -421,40 +579,19 @@ async def request( } ) ), - params=encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get( - "additional_query_parameters", {} - ) - or {} - if request_options is not None - else {} - ), - }, - omit, - ) - ) - ) - ), + params=_encoded_params if _encoded_params else None, json=json_body, data=data_body, content=content, - files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) - if files is not None - else None, + files=request_files, timeout=timeout, ) max_retries: int = ( - request_options.get("max_retries", 0) if request_options is not None else 0 + request_options.get("max_retries", 2) if request_options is not None else 2 ) if _should_retry(response=response): - if max_retries > retries: + if retries < max_retries: await asyncio.sleep(_retry_timeout(response=response, retries=retries)) return await self.request( path=path, @@ -485,32 +622,76 @@ async def stream( typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]] ] = None, files: typing.Optional[ - typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]] + typing.Union[ + typing.Dict[ + str, typing.Optional[typing.Union[File, typing.List[File]]] + ], + typing.List[typing.Tuple[str, File]], + ] ] = None, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, ) -> typing.AsyncIterator[httpx.Response]: base_url = self.get_base_url(base_url) timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None - else self.base_timeout + else self.base_timeout() ) + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples( + remove_omit_from_dict(remove_none_from_dict(files), omit) + ) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + json_body, data_body = get_request_body( json=json, data=data, request_options=request_options, omit=omit ) + data_body = _maybe_filter_none_from_multipart_data( + data_body, request_files, force_multipart + ) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ) + async with self.httpx_client.stream( method=method, url=urllib.parse.urljoin(f"{base_url}/", path), headers=jsonable_encoder( remove_none_from_dict( { - **self.base_headers, + **_headers, **(headers if headers is not None else {}), **( request_options.get("additional_headers", {}) @@ -520,31 +701,11 @@ async def stream( } ) ), - params=encode_query( - jsonable_encoder( - remove_none_from_dict( - remove_omit_from_dict( - { - **(params if params is not None else {}), - **( - request_options.get( - "additional_query_parameters", {} - ) - if request_options is not None - else {} - ), - }, - omit=omit, - ) - ) - ) - ), + params=_encoded_params if _encoded_params else None, json=json_body, data=data_body, content=content, - files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) - if files is not None - else None, + files=request_files, timeout=timeout, ) as stream: yield stream diff --git a/langfuse/api/core/http_response.py b/langfuse/api/core/http_response.py new file mode 100644 index 000000000..2479747e8 --- /dev/null +++ b/langfuse/api/core/http_response.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Dict, Generic, TypeVar + +import httpx + +# Generic to represent the underlying type of the data wrapped by the HTTP response. +T = TypeVar("T") + + +class BaseHttpResponse: + """Minimalist HTTP response wrapper that exposes response headers.""" + + _response: httpx.Response + + def __init__(self, response: httpx.Response): + self._response = response + + @property + def headers(self) -> Dict[str, str]: + return dict(self._response.headers) + + +class HttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + def close(self) -> None: + self._response.close() + + +class AsyncHttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + async def close(self) -> None: + await self._response.aclose() diff --git a/langfuse/api/core/http_sse/__init__.py b/langfuse/api/core/http_sse/__init__.py new file mode 100644 index 000000000..ab0b4995a --- /dev/null +++ b/langfuse/api/core/http_sse/__init__.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from ._api import EventSource, aconnect_sse, connect_sse + from ._exceptions import SSEError + from ._models import ServerSentEvent +_dynamic_imports: typing.Dict[str, str] = { + "EventSource": "._api", + "SSEError": "._exceptions", + "ServerSentEvent": "._models", + "aconnect_sse": "._api", + "connect_sse": "._api", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/langfuse/api/core/http_sse/_api.py b/langfuse/api/core/http_sse/_api.py new file mode 100644 index 000000000..eb739a22b --- /dev/null +++ b/langfuse/api/core/http_sse/_api.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +import re +from contextlib import asynccontextmanager, contextmanager +from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast + +import httpx +from ._decoders import SSEDecoder +from ._exceptions import SSEError +from ._models import ServerSentEvent + + +class EventSource: + def __init__(self, response: httpx.Response) -> None: + self._response = response + + def _check_content_type(self) -> None: + content_type = self._response.headers.get("content-type", "").partition(";")[0] + if "text/event-stream" not in content_type: + raise SSEError( + f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" + ) + + def _get_charset(self) -> str: + """Extract charset from Content-Type header, fallback to UTF-8.""" + content_type = self._response.headers.get("content-type", "") + + # Parse charset parameter using regex + charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) + if charset_match: + charset = charset_match.group(1).strip("\"'") + # Validate that it's a known encoding + try: + # Test if the charset is valid by trying to encode/decode + "test".encode(charset).decode(charset) + return charset + except (LookupError, UnicodeError): + # If charset is invalid, fall back to UTF-8 + pass + + # Default to UTF-8 if no charset specified or invalid charset + return "utf-8" + + @property + def response(self) -> httpx.Response: + return self._response + + def iter_sse(self) -> Iterator[ServerSentEvent]: + self._check_content_type() + decoder = SSEDecoder() + charset = self._get_charset() + + buffer = "" + for chunk in self._response.iter_bytes(): + # Decode chunk using detected charset + text_chunk = chunk.decode(charset, errors="replace") + buffer += text_chunk + + # Process complete lines + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) + line = line.rstrip("\r") + sse = decoder.decode(line) + # when we reach a "\n\n" => line = '' + # => decoder will attempt to return an SSE Event + if sse is not None: + yield sse + + # Process any remaining data in buffer + if buffer.strip(): + line = buffer.rstrip("\r") + sse = decoder.decode(line) + if sse is not None: + yield sse + + async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: + self._check_content_type() + decoder = SSEDecoder() + lines = cast(AsyncGenerator[str, None], self._response.aiter_lines()) + try: + async for line in lines: + line = line.rstrip("\n") + sse = decoder.decode(line) + if sse is not None: + yield sse + finally: + await lines.aclose() + + +@contextmanager +def connect_sse( + client: httpx.Client, method: str, url: str, **kwargs: Any +) -> Iterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +@asynccontextmanager +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: str, + **kwargs: Any, +) -> AsyncIterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + async with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) diff --git a/langfuse/api/core/http_sse/_decoders.py b/langfuse/api/core/http_sse/_decoders.py new file mode 100644 index 000000000..bdec57b44 --- /dev/null +++ b/langfuse/api/core/http_sse/_decoders.py @@ -0,0 +1,66 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import List, Optional + +from ._models import ServerSentEvent + + +class SSEDecoder: + def __init__(self) -> None: + self._event = "" + self._data: List[str] = [] + self._last_event_id = "" + self._retry: Optional[int] = None + + def decode(self, line: str) -> Optional[ServerSentEvent]: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if ( + not self._event + and not self._data + and not self._last_event_id + and self._retry is None + ): + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = "" + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None diff --git a/langfuse/api/resources/utils/resources/pagination/__init__.py b/langfuse/api/core/http_sse/_exceptions.py similarity index 51% rename from langfuse/api/resources/utils/resources/pagination/__init__.py rename to langfuse/api/core/http_sse/_exceptions.py index 9bd1e5f71..81605a8a6 100644 --- a/langfuse/api/resources/utils/resources/pagination/__init__.py +++ b/langfuse/api/core/http_sse/_exceptions.py @@ -1,5 +1,7 @@ # This file was auto-generated by Fern from our API Definition. -from .types import MetaResponse +import httpx -__all__ = ["MetaResponse"] + +class SSEError(httpx.TransportError): + pass diff --git a/langfuse/api/core/http_sse/_models.py b/langfuse/api/core/http_sse/_models.py new file mode 100644 index 000000000..1af57f8fd --- /dev/null +++ b/langfuse/api/core/http_sse/_models.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass(frozen=True) +class ServerSentEvent: + event: str = "message" + data: str = "" + id: str = "" + retry: Optional[int] = None + + def json(self) -> Any: + """Parse the data field as JSON.""" + return json.loads(self.data) diff --git a/langfuse/api/core/jsonable_encoder.py b/langfuse/api/core/jsonable_encoder.py index 7a05e9190..90f53dfa7 100644 --- a/langfuse/api/core/jsonable_encoder.py +++ b/langfuse/api/core/jsonable_encoder.py @@ -11,35 +11,23 @@ import base64 import dataclasses import datetime as dt -from collections import defaultdict from enum import Enum from pathlib import PurePath from types import GeneratorType -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Set, Union +import pydantic from .datetime_utils import serialize_datetime -from .pydantic_utilities import pydantic_v1 +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + encode_by_type, + to_jsonable_with_fallback, +) SetIntStr = Set[Union[int, str]] DictIntStrAny = Dict[Union[int, str], Any] -def generate_encoders_by_class_tuples( - type_encoder_map: Dict[Any, Callable[[Any], Any]], -) -> Dict[Callable[[Any], Any], Tuple[Any, ...]]: - encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict( - tuple - ) - for type_, encoder in type_encoder_map.items(): - encoders_by_class_tuples[encoder] += (type_,) - return encoders_by_class_tuples - - -encoders_by_class_tuples = generate_encoders_by_class_tuples( - pydantic_v1.json.ENCODERS_BY_TYPE -) - - def jsonable_encoder( obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None ) -> Any: @@ -51,16 +39,21 @@ def jsonable_encoder( for encoder_type, encoder_instance in custom_encoder.items(): if isinstance(obj, encoder_type): return encoder_instance(obj) - if isinstance(obj, pydantic_v1.BaseModel): - encoder = getattr(obj.__config__, "json_encoders", {}) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 if custom_encoder: encoder.update(custom_encoder) obj_dict = obj.dict(by_alias=True) if "__root__" in obj_dict: obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] return jsonable_encoder(obj_dict, custom_encoder=encoder) if dataclasses.is_dataclass(obj): - obj_dict = dataclasses.asdict(obj) + obj_dict = dataclasses.asdict(obj) # type: ignore return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) if isinstance(obj, bytes): return base64.b64encode(obj).decode("utf-8") @@ -89,20 +82,21 @@ def jsonable_encoder( encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) return encoded_list - if type(obj) in pydantic_v1.json.ENCODERS_BY_TYPE: - return pydantic_v1.json.ENCODERS_BY_TYPE[type(obj)](obj) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(obj, classes_tuple): - return encoder(obj) + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode - try: - data = dict(obj) - except Exception as e: - errors: List[Exception] = [] - errors.append(e) try: - data = vars(obj) + data = dict(o) except Exception as e: + errors: List[Exception] = [] errors.append(e) - raise ValueError(errors) from e - return jsonable_encoder(data, custom_encoder=custom_encoder) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) diff --git a/langfuse/api/core/pydantic_utilities.py b/langfuse/api/core/pydantic_utilities.py index a72c1a52f..d2b7b51b6 100644 --- a/langfuse/api/core/pydantic_utilities.py +++ b/langfuse/api/core/pydantic_utilities.py @@ -1,28 +1,310 @@ # This file was auto-generated by Fern from our API Definition. -import typing +# nopycln: file +import datetime as dt +from collections import defaultdict +from typing import ( + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) import pydantic IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") if IS_PYDANTIC_V2: - import pydantic.v1 as pydantic_v1 # type: ignore # nopycln: import + from pydantic.v1.datetime_parse import parse_date as parse_date + from pydantic.v1.datetime_parse import parse_datetime as parse_datetime + from pydantic.v1.fields import ModelField as ModelField + from pydantic.v1.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[attr-defined] + from pydantic.v1.typing import get_args as get_args + from pydantic.v1.typing import get_origin as get_origin + from pydantic.v1.typing import is_literal_type as is_literal_type + from pydantic.v1.typing import is_union as is_union else: - import pydantic as pydantic_v1 # type: ignore # nopycln: import + from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef] + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] + from pydantic.typing import get_args as get_args # type: ignore[no-redef] + from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + dealiased_object = convert_and_respect_annotation_metadata( + object_=object_, annotation=type_, direction="read" + ) + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + return adapter.validate_python(dealiased_object) + return pydantic.parse_obj_as(type_, dealiased_object) + + +def to_jsonable_with_fallback( + obj: Any, fallback_serializer: Callable[[Any], Any] +) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + serialized = self.dict() # type: ignore[attr-defined] + data = { + k: serialize_datetime(v) if isinstance(v, dt.datetime) else v + for k, v in serialized.items() + } + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @classmethod + def model_construct( + cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any + ) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata( + object_=values, annotation=cls, direction="read" + ) + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct( + cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any + ) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata( + object_=values, annotation=cls, direction="read" + ) + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] + return super().construct(_fields_set, **dealiased_object) + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ( + "exclude_unset" in kwargs and not kwargs["exclude_unset"] + ): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict( + **kwargs_with_defaults_exclude_unset_include_fields + ) + + return cast( + Dict[str, Any], + convert_and_respect_annotation_metadata( + object_=dict_dump, annotation=self.__class__, direction="write" + ), + ) + + +def _union_list_of_pydantic_dicts( + source: List[Any], destination: List[Any] +) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append( + _union_list_of_pydantic_dicts(item, destination_value) + ) + else: + converted_list.append(item) + return converted_list def deep_union_pydantic_dicts( - source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] -) -> typing.Dict[str, typing.Any]: + source: Dict[str, Any], destination: Dict[str, Any] +) -> Dict[str, Any]: for key, value in source.items(): + node = destination.setdefault(key, {}) if isinstance(value, dict): - node = destination.setdefault(key, {}) deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) else: destination[key] = value return destination -__all__ = ["pydantic_v1"] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict( + tuple + ) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator( + field_name: str, pre: bool = False +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast( + AnyCallable, + pydantic.field_validator(field_name, mode="before" if pre else "after")( + func + ), + ) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, pydantic.fields.FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/langfuse/api/core/query_encoder.py b/langfuse/api/core/query_encoder.py index 069633086..03fbf59bd 100644 --- a/langfuse/api/core/query_encoder.py +++ b/langfuse/api/core/query_encoder.py @@ -1,39 +1,60 @@ # This file was auto-generated by Fern from our API Definition. -from collections import ChainMap -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple -from .pydantic_utilities import pydantic_v1 +import pydantic # Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict def traverse_query_dict( dict_flat: Dict[str, Any], key_prefix: Optional[str] = None -) -> Dict[str, Any]: - result = {} +) -> List[Tuple[str, Any]]: + result = [] for k, v in dict_flat.items(): key = f"{key_prefix}[{k}]" if key_prefix is not None else k if isinstance(v, dict): - result.update(traverse_query_dict(v, key)) + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, arr_v)) else: - result[key] = v + result.append((key, v)) return result -def single_query_encoder(query_key: str, query_value: Any) -> Dict[str, Any]: - if isinstance(query_value, pydantic_v1.BaseModel) or isinstance(query_value, dict): - if isinstance(query_value, pydantic_v1.BaseModel): +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): obj_dict = query_value.dict(by_alias=True) else: obj_dict = query_value return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value - return {query_key: query_value} + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, value)) + return encoded_values -def encode_query(query: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: - return ( - dict(ChainMap(*[single_query_encoder(k, v) for k, v in query.items()])) - if query is not None - else None - ) + return [(query_key, query_value)] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/langfuse/api/core/request_options.py b/langfuse/api/core/request_options.py index d0bf0dbce..1b3880443 100644 --- a/langfuse/api/core/request_options.py +++ b/langfuse/api/core/request_options.py @@ -23,6 +23,8 @@ class RequestOptions(typing.TypedDict, total=False): - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + + - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. """ timeout_in_seconds: NotRequired[int] @@ -30,3 +32,4 @@ class RequestOptions(typing.TypedDict, total=False): additional_headers: NotRequired[typing.Dict[str, typing.Any]] additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] + chunk_size: NotRequired[int] diff --git a/langfuse/api/core/serialization.py b/langfuse/api/core/serialization.py new file mode 100644 index 000000000..ad6eb8d7f --- /dev/null +++ b/langfuse/api/core/serialization.py @@ -0,0 +1,282 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance( + object_, typing.Mapping + ): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + try: + annotations = typing_extensions.get_type_hints( + expected_type, include_extras=True + ) + except NameError: + # The TypedDict contains a circular reference, so + # we use the __annotations__ attribute directly. + annotations = getattr(expected_type, "__annotations__", {}) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[ + _alias_key(key, type_, direction, aliases_to_field_names) + ] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/langfuse/api/dataset_items/__init__.py b/langfuse/api/dataset_items/__init__.py new file mode 100644 index 000000000..4d009d228 --- /dev/null +++ b/langfuse/api/dataset_items/__init__.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateDatasetItemRequest, + DeleteDatasetItemResponse, + PaginatedDatasetItems, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreateDatasetItemRequest": ".types", + "DeleteDatasetItemResponse": ".types", + "PaginatedDatasetItems": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateDatasetItemRequest", + "DeleteDatasetItemResponse", + "PaginatedDatasetItems", +] diff --git a/langfuse/api/dataset_items/client.py b/langfuse/api/dataset_items/client.py new file mode 100644 index 000000000..c44ca24d8 --- /dev/null +++ b/langfuse/api/dataset_items/client.py @@ -0,0 +1,499 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..commons.types.dataset_item import DatasetItem +from ..commons.types.dataset_status import DatasetStatus +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawDatasetItemsClient, RawDatasetItemsClient +from .types.delete_dataset_item_response import DeleteDatasetItemResponse +from .types.paginated_dataset_items import PaginatedDatasetItems + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class DatasetItemsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawDatasetItemsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawDatasetItemsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawDatasetItemsClient + """ + return self._raw_client + + def create( + self, + *, + dataset_name: str, + input: typing.Optional[typing.Any] = OMIT, + expected_output: typing.Optional[typing.Any] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + source_trace_id: typing.Optional[str] = OMIT, + source_observation_id: typing.Optional[str] = OMIT, + id: typing.Optional[str] = OMIT, + status: typing.Optional[DatasetStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> DatasetItem: + """ + Create a dataset item + + Parameters + ---------- + dataset_name : str + + input : typing.Optional[typing.Any] + + expected_output : typing.Optional[typing.Any] + + metadata : typing.Optional[typing.Any] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + id : typing.Optional[str] + Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. + + status : typing.Optional[DatasetStatus] + Defaults to ACTIVE for newly created items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetItem + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.dataset_items.create( + dataset_name="datasetName", + ) + """ + _response = self._raw_client.create( + dataset_name=dataset_name, + input=input, + expected_output=expected_output, + metadata=metadata, + source_trace_id=source_trace_id, + source_observation_id=source_observation_id, + id=id, + status=status, + request_options=request_options, + ) + return _response.data + + def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DatasetItem: + """ + Get a dataset item + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetItem + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.dataset_items.get( + id="id", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def list( + self, + *, + dataset_name: typing.Optional[str] = None, + source_trace_id: typing.Optional[str] = None, + source_observation_id: typing.Optional[str] = None, + version: typing.Optional[dt.datetime] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasetItems: + """ + Get dataset items. Optionally specify a version to get the items as they existed at that point in time. + Note: If version parameter is provided, datasetName must also be provided. + + Parameters + ---------- + dataset_name : typing.Optional[str] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + If provided, returns state of dataset at this timestamp. + If not provided, returns the latest version. Requires datasetName to be specified. + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasetItems + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.dataset_items.list() + """ + _response = self._raw_client.list( + dataset_name=dataset_name, + source_trace_id=source_trace_id, + source_observation_id=source_observation_id, + version=version, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteDatasetItemResponse: + """ + Delete a dataset item and all its run items. This action is irreversible. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteDatasetItemResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.dataset_items.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + +class AsyncDatasetItemsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawDatasetItemsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawDatasetItemsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawDatasetItemsClient + """ + return self._raw_client + + async def create( + self, + *, + dataset_name: str, + input: typing.Optional[typing.Any] = OMIT, + expected_output: typing.Optional[typing.Any] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + source_trace_id: typing.Optional[str] = OMIT, + source_observation_id: typing.Optional[str] = OMIT, + id: typing.Optional[str] = OMIT, + status: typing.Optional[DatasetStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> DatasetItem: + """ + Create a dataset item + + Parameters + ---------- + dataset_name : str + + input : typing.Optional[typing.Any] + + expected_output : typing.Optional[typing.Any] + + metadata : typing.Optional[typing.Any] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + id : typing.Optional[str] + Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. + + status : typing.Optional[DatasetStatus] + Defaults to ACTIVE for newly created items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetItem + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.dataset_items.create( + dataset_name="datasetName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + dataset_name=dataset_name, + input=input, + expected_output=expected_output, + metadata=metadata, + source_trace_id=source_trace_id, + source_observation_id=source_observation_id, + id=id, + status=status, + request_options=request_options, + ) + return _response.data + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DatasetItem: + """ + Get a dataset item + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetItem + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.dataset_items.get( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def list( + self, + *, + dataset_name: typing.Optional[str] = None, + source_trace_id: typing.Optional[str] = None, + source_observation_id: typing.Optional[str] = None, + version: typing.Optional[dt.datetime] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasetItems: + """ + Get dataset items. Optionally specify a version to get the items as they existed at that point in time. + Note: If version parameter is provided, datasetName must also be provided. + + Parameters + ---------- + dataset_name : typing.Optional[str] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + If provided, returns state of dataset at this timestamp. + If not provided, returns the latest version. Requires datasetName to be specified. + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasetItems + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.dataset_items.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + dataset_name=dataset_name, + source_trace_id=source_trace_id, + source_observation_id=source_observation_id, + version=version, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteDatasetItemResponse: + """ + Delete a dataset item and all its run items. This action is irreversible. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteDatasetItemResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.dataset_items.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/langfuse/api/dataset_items/raw_client.py b/langfuse/api/dataset_items/raw_client.py new file mode 100644 index 000000000..6aeafb54d --- /dev/null +++ b/langfuse/api/dataset_items/raw_client.py @@ -0,0 +1,973 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.dataset_item import DatasetItem +from ..commons.types.dataset_status import DatasetStatus +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.delete_dataset_item_response import DeleteDatasetItemResponse +from .types.paginated_dataset_items import PaginatedDatasetItems + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawDatasetItemsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + dataset_name: str, + input: typing.Optional[typing.Any] = OMIT, + expected_output: typing.Optional[typing.Any] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + source_trace_id: typing.Optional[str] = OMIT, + source_observation_id: typing.Optional[str] = OMIT, + id: typing.Optional[str] = OMIT, + status: typing.Optional[DatasetStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DatasetItem]: + """ + Create a dataset item + + Parameters + ---------- + dataset_name : str + + input : typing.Optional[typing.Any] + + expected_output : typing.Optional[typing.Any] + + metadata : typing.Optional[typing.Any] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + id : typing.Optional[str] + Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. + + status : typing.Optional[DatasetStatus] + Defaults to ACTIVE for newly created items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DatasetItem] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/dataset-items", + method="POST", + json={ + "datasetName": dataset_name, + "input": input, + "expectedOutput": expected_output, + "metadata": metadata, + "sourceTraceId": source_trace_id, + "sourceObservationId": source_observation_id, + "id": id, + "status": status, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetItem, + parse_obj_as( + type_=DatasetItem, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DatasetItem]: + """ + Get a dataset item + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DatasetItem] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/dataset-items/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetItem, + parse_obj_as( + type_=DatasetItem, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list( + self, + *, + dataset_name: typing.Optional[str] = None, + source_trace_id: typing.Optional[str] = None, + source_observation_id: typing.Optional[str] = None, + version: typing.Optional[dt.datetime] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedDatasetItems]: + """ + Get dataset items. Optionally specify a version to get the items as they existed at that point in time. + Note: If version parameter is provided, datasetName must also be provided. + + Parameters + ---------- + dataset_name : typing.Optional[str] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + If provided, returns state of dataset at this timestamp. + If not provided, returns the latest version. Requires datasetName to be specified. + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedDatasetItems] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/dataset-items", + method="GET", + params={ + "datasetName": dataset_name, + "sourceTraceId": source_trace_id, + "sourceObservationId": source_observation_id, + "version": serialize_datetime(version) if version is not None else None, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasetItems, + parse_obj_as( + type_=PaginatedDatasetItems, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteDatasetItemResponse]: + """ + Delete a dataset item and all its run items. This action is irreversible. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteDatasetItemResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/dataset-items/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteDatasetItemResponse, + parse_obj_as( + type_=DeleteDatasetItemResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawDatasetItemsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + dataset_name: str, + input: typing.Optional[typing.Any] = OMIT, + expected_output: typing.Optional[typing.Any] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + source_trace_id: typing.Optional[str] = OMIT, + source_observation_id: typing.Optional[str] = OMIT, + id: typing.Optional[str] = OMIT, + status: typing.Optional[DatasetStatus] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DatasetItem]: + """ + Create a dataset item + + Parameters + ---------- + dataset_name : str + + input : typing.Optional[typing.Any] + + expected_output : typing.Optional[typing.Any] + + metadata : typing.Optional[typing.Any] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + id : typing.Optional[str] + Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. + + status : typing.Optional[DatasetStatus] + Defaults to ACTIVE for newly created items + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DatasetItem] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/dataset-items", + method="POST", + json={ + "datasetName": dataset_name, + "input": input, + "expectedOutput": expected_output, + "metadata": metadata, + "sourceTraceId": source_trace_id, + "sourceObservationId": source_observation_id, + "id": id, + "status": status, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetItem, + parse_obj_as( + type_=DatasetItem, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DatasetItem]: + """ + Get a dataset item + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DatasetItem] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/dataset-items/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetItem, + parse_obj_as( + type_=DatasetItem, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list( + self, + *, + dataset_name: typing.Optional[str] = None, + source_trace_id: typing.Optional[str] = None, + source_observation_id: typing.Optional[str] = None, + version: typing.Optional[dt.datetime] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedDatasetItems]: + """ + Get dataset items. Optionally specify a version to get the items as they existed at that point in time. + Note: If version parameter is provided, datasetName must also be provided. + + Parameters + ---------- + dataset_name : typing.Optional[str] + + source_trace_id : typing.Optional[str] + + source_observation_id : typing.Optional[str] + + version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + If provided, returns state of dataset at this timestamp. + If not provided, returns the latest version. Requires datasetName to be specified. + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedDatasetItems] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/dataset-items", + method="GET", + params={ + "datasetName": dataset_name, + "sourceTraceId": source_trace_id, + "sourceObservationId": source_observation_id, + "version": serialize_datetime(version) if version is not None else None, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasetItems, + parse_obj_as( + type_=PaginatedDatasetItems, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteDatasetItemResponse]: + """ + Delete a dataset item and all its run items. This action is irreversible. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteDatasetItemResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/dataset-items/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteDatasetItemResponse, + parse_obj_as( + type_=DeleteDatasetItemResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/dataset_items/types/__init__.py b/langfuse/api/dataset_items/types/__init__.py new file mode 100644 index 000000000..c7ce59bf4 --- /dev/null +++ b/langfuse/api/dataset_items/types/__init__.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_dataset_item_request import CreateDatasetItemRequest + from .delete_dataset_item_response import DeleteDatasetItemResponse + from .paginated_dataset_items import PaginatedDatasetItems +_dynamic_imports: typing.Dict[str, str] = { + "CreateDatasetItemRequest": ".create_dataset_item_request", + "DeleteDatasetItemResponse": ".delete_dataset_item_response", + "PaginatedDatasetItems": ".paginated_dataset_items", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateDatasetItemRequest", + "DeleteDatasetItemResponse", + "PaginatedDatasetItems", +] diff --git a/langfuse/api/dataset_items/types/create_dataset_item_request.py b/langfuse/api/dataset_items/types/create_dataset_item_request.py new file mode 100644 index 000000000..b778e42ae --- /dev/null +++ b/langfuse/api/dataset_items/types/create_dataset_item_request.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...commons.types.dataset_status import DatasetStatus +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateDatasetItemRequest(UniversalBaseModel): + dataset_name: typing_extensions.Annotated[str, FieldMetadata(alias="datasetName")] + input: typing.Optional[typing.Any] = None + expected_output: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="expectedOutput") + ] = None + metadata: typing.Optional[typing.Any] = None + source_trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sourceTraceId") + ] = None + source_observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sourceObservationId") + ] = None + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. + """ + + status: typing.Optional[DatasetStatus] = pydantic.Field(default=None) + """ + Defaults to ACTIVE for newly created items + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/dataset_items/types/delete_dataset_item_response.py b/langfuse/api/dataset_items/types/delete_dataset_item_response.py new file mode 100644 index 000000000..982e4f2dd --- /dev/null +++ b/langfuse/api/dataset_items/types/delete_dataset_item_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class DeleteDatasetItemResponse(UniversalBaseModel): + message: str = pydantic.Field() + """ + Success message after deletion + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/dataset_items/types/paginated_dataset_items.py b/langfuse/api/dataset_items/types/paginated_dataset_items.py new file mode 100644 index 000000000..63a683787 --- /dev/null +++ b/langfuse/api/dataset_items/types/paginated_dataset_items.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.dataset_item import DatasetItem +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class PaginatedDatasetItems(UniversalBaseModel): + data: typing.List[DatasetItem] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/dataset_run_items/__init__.py b/langfuse/api/dataset_run_items/__init__.py new file mode 100644 index 000000000..9ff4e097e --- /dev/null +++ b/langfuse/api/dataset_run_items/__init__.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateDatasetRunItemRequest, PaginatedDatasetRunItems +_dynamic_imports: typing.Dict[str, str] = { + "CreateDatasetRunItemRequest": ".types", + "PaginatedDatasetRunItems": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateDatasetRunItemRequest", "PaginatedDatasetRunItems"] diff --git a/langfuse/api/dataset_run_items/client.py b/langfuse/api/dataset_run_items/client.py new file mode 100644 index 000000000..cbafacc67 --- /dev/null +++ b/langfuse/api/dataset_run_items/client.py @@ -0,0 +1,323 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..commons.types.dataset_run_item import DatasetRunItem +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawDatasetRunItemsClient, RawDatasetRunItemsClient +from .types.paginated_dataset_run_items import PaginatedDatasetRunItems + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class DatasetRunItemsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawDatasetRunItemsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawDatasetRunItemsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawDatasetRunItemsClient + """ + return self._raw_client + + def create( + self, + *, + run_name: str, + dataset_item_id: str, + run_description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + observation_id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + dataset_version: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> DatasetRunItem: + """ + Create a dataset run item + + Parameters + ---------- + run_name : str + + dataset_item_id : str + + run_description : typing.Optional[str] + Description of the run. If run exists, description will be updated. + + metadata : typing.Optional[typing.Any] + Metadata of the dataset run, updates run if run already exists + + observation_id : typing.Optional[str] + + trace_id : typing.Optional[str] + traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. + + dataset_version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + Specifies the dataset version to use for this experiment run. + If provided, the experiment will use dataset items as they existed at or before this timestamp. + If not provided, uses the latest version of dataset items. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetRunItem + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.dataset_run_items.create( + run_name="runName", + dataset_item_id="datasetItemId", + ) + """ + _response = self._raw_client.create( + run_name=run_name, + dataset_item_id=dataset_item_id, + run_description=run_description, + metadata=metadata, + observation_id=observation_id, + trace_id=trace_id, + dataset_version=dataset_version, + request_options=request_options, + ) + return _response.data + + def list( + self, + *, + dataset_id: str, + run_name: str, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasetRunItems: + """ + List dataset run items + + Parameters + ---------- + dataset_id : str + + run_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasetRunItems + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.dataset_run_items.list( + dataset_id="datasetId", + run_name="runName", + ) + """ + _response = self._raw_client.list( + dataset_id=dataset_id, + run_name=run_name, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + +class AsyncDatasetRunItemsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawDatasetRunItemsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawDatasetRunItemsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawDatasetRunItemsClient + """ + return self._raw_client + + async def create( + self, + *, + run_name: str, + dataset_item_id: str, + run_description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + observation_id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + dataset_version: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> DatasetRunItem: + """ + Create a dataset run item + + Parameters + ---------- + run_name : str + + dataset_item_id : str + + run_description : typing.Optional[str] + Description of the run. If run exists, description will be updated. + + metadata : typing.Optional[typing.Any] + Metadata of the dataset run, updates run if run already exists + + observation_id : typing.Optional[str] + + trace_id : typing.Optional[str] + traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. + + dataset_version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + Specifies the dataset version to use for this experiment run. + If provided, the experiment will use dataset items as they existed at or before this timestamp. + If not provided, uses the latest version of dataset items. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetRunItem + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.dataset_run_items.create( + run_name="runName", + dataset_item_id="datasetItemId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + run_name=run_name, + dataset_item_id=dataset_item_id, + run_description=run_description, + metadata=metadata, + observation_id=observation_id, + trace_id=trace_id, + dataset_version=dataset_version, + request_options=request_options, + ) + return _response.data + + async def list( + self, + *, + dataset_id: str, + run_name: str, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasetRunItems: + """ + List dataset run items + + Parameters + ---------- + dataset_id : str + + run_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasetRunItems + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.dataset_run_items.list( + dataset_id="datasetId", + run_name="runName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + dataset_id=dataset_id, + run_name=run_name, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/dataset_run_items/raw_client.py b/langfuse/api/dataset_run_items/raw_client.py new file mode 100644 index 000000000..894adbfff --- /dev/null +++ b/langfuse/api/dataset_run_items/raw_client.py @@ -0,0 +1,547 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.dataset_run_item import DatasetRunItem +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.paginated_dataset_run_items import PaginatedDatasetRunItems + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawDatasetRunItemsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + run_name: str, + dataset_item_id: str, + run_description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + observation_id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + dataset_version: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DatasetRunItem]: + """ + Create a dataset run item + + Parameters + ---------- + run_name : str + + dataset_item_id : str + + run_description : typing.Optional[str] + Description of the run. If run exists, description will be updated. + + metadata : typing.Optional[typing.Any] + Metadata of the dataset run, updates run if run already exists + + observation_id : typing.Optional[str] + + trace_id : typing.Optional[str] + traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. + + dataset_version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + Specifies the dataset version to use for this experiment run. + If provided, the experiment will use dataset items as they existed at or before this timestamp. + If not provided, uses the latest version of dataset items. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DatasetRunItem] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/dataset-run-items", + method="POST", + json={ + "runName": run_name, + "runDescription": run_description, + "metadata": metadata, + "datasetItemId": dataset_item_id, + "observationId": observation_id, + "traceId": trace_id, + "datasetVersion": dataset_version, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetRunItem, + parse_obj_as( + type_=DatasetRunItem, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list( + self, + *, + dataset_id: str, + run_name: str, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedDatasetRunItems]: + """ + List dataset run items + + Parameters + ---------- + dataset_id : str + + run_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedDatasetRunItems] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/dataset-run-items", + method="GET", + params={ + "datasetId": dataset_id, + "runName": run_name, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasetRunItems, + parse_obj_as( + type_=PaginatedDatasetRunItems, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawDatasetRunItemsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + run_name: str, + dataset_item_id: str, + run_description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + observation_id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + dataset_version: typing.Optional[dt.datetime] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DatasetRunItem]: + """ + Create a dataset run item + + Parameters + ---------- + run_name : str + + dataset_item_id : str + + run_description : typing.Optional[str] + Description of the run. If run exists, description will be updated. + + metadata : typing.Optional[typing.Any] + Metadata of the dataset run, updates run if run already exists + + observation_id : typing.Optional[str] + + trace_id : typing.Optional[str] + traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. + + dataset_version : typing.Optional[dt.datetime] + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + Specifies the dataset version to use for this experiment run. + If provided, the experiment will use dataset items as they existed at or before this timestamp. + If not provided, uses the latest version of dataset items. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DatasetRunItem] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/dataset-run-items", + method="POST", + json={ + "runName": run_name, + "runDescription": run_description, + "metadata": metadata, + "datasetItemId": dataset_item_id, + "observationId": observation_id, + "traceId": trace_id, + "datasetVersion": dataset_version, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetRunItem, + parse_obj_as( + type_=DatasetRunItem, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list( + self, + *, + dataset_id: str, + run_name: str, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedDatasetRunItems]: + """ + List dataset run items + + Parameters + ---------- + dataset_id : str + + run_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedDatasetRunItems] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/dataset-run-items", + method="GET", + params={ + "datasetId": dataset_id, + "runName": run_name, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasetRunItems, + parse_obj_as( + type_=PaginatedDatasetRunItems, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/dataset_run_items/types/__init__.py b/langfuse/api/dataset_run_items/types/__init__.py new file mode 100644 index 000000000..b520924c0 --- /dev/null +++ b/langfuse/api/dataset_run_items/types/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_dataset_run_item_request import CreateDatasetRunItemRequest + from .paginated_dataset_run_items import PaginatedDatasetRunItems +_dynamic_imports: typing.Dict[str, str] = { + "CreateDatasetRunItemRequest": ".create_dataset_run_item_request", + "PaginatedDatasetRunItems": ".paginated_dataset_run_items", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateDatasetRunItemRequest", "PaginatedDatasetRunItems"] diff --git a/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py b/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py new file mode 100644 index 000000000..43a586249 --- /dev/null +++ b/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateDatasetRunItemRequest(UniversalBaseModel): + run_name: typing_extensions.Annotated[str, FieldMetadata(alias="runName")] + run_description: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="runDescription") + ] = pydantic.Field(default=None) + """ + Description of the run. If run exists, description will be updated. + """ + + metadata: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Metadata of the dataset run, updates run if run already exists + """ + + dataset_item_id: typing_extensions.Annotated[ + str, FieldMetadata(alias="datasetItemId") + ] + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = pydantic.Field(default=None) + """ + traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. + """ + + dataset_version: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="datasetVersion") + ] = pydantic.Field(default=None) + """ + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). + Specifies the dataset version to use for this experiment run. + If provided, the experiment will use dataset items as they existed at or before this timestamp. + If not provided, uses the latest version of dataset items. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/dataset_run_items/types/paginated_dataset_run_items.py b/langfuse/api/dataset_run_items/types/paginated_dataset_run_items.py new file mode 100644 index 000000000..24f7c1996 --- /dev/null +++ b/langfuse/api/dataset_run_items/types/paginated_dataset_run_items.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.dataset_run_item import DatasetRunItem +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class PaginatedDatasetRunItems(UniversalBaseModel): + data: typing.List[DatasetRunItem] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/datasets/__init__.py b/langfuse/api/datasets/__init__.py new file mode 100644 index 000000000..e9005285e --- /dev/null +++ b/langfuse/api/datasets/__init__.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateDatasetRequest, + DeleteDatasetRunResponse, + PaginatedDatasetRuns, + PaginatedDatasets, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreateDatasetRequest": ".types", + "DeleteDatasetRunResponse": ".types", + "PaginatedDatasetRuns": ".types", + "PaginatedDatasets": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateDatasetRequest", + "DeleteDatasetRunResponse", + "PaginatedDatasetRuns", + "PaginatedDatasets", +] diff --git a/langfuse/api/datasets/client.py b/langfuse/api/datasets/client.py new file mode 100644 index 000000000..859ee0fe7 --- /dev/null +++ b/langfuse/api/datasets/client.py @@ -0,0 +1,661 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..commons.types.dataset import Dataset +from ..commons.types.dataset_run_with_items import DatasetRunWithItems +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawDatasetsClient, RawDatasetsClient +from .types.delete_dataset_run_response import DeleteDatasetRunResponse +from .types.paginated_dataset_runs import PaginatedDatasetRuns +from .types.paginated_datasets import PaginatedDatasets + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class DatasetsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawDatasetsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawDatasetsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawDatasetsClient + """ + return self._raw_client + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasets: + """ + Get all datasets + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasets + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.datasets.list() + """ + _response = self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get( + self, + dataset_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> Dataset: + """ + Get a dataset + + Parameters + ---------- + dataset_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Dataset + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.datasets.get( + dataset_name="datasetName", + ) + """ + _response = self._raw_client.get(dataset_name, request_options=request_options) + return _response.data + + def create( + self, + *, + name: str, + description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + input_schema: typing.Optional[typing.Any] = OMIT, + expected_output_schema: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Dataset: + """ + Create a dataset + + Parameters + ---------- + name : str + + description : typing.Optional[str] + + metadata : typing.Optional[typing.Any] + + input_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. + + expected_output_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Dataset + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.datasets.create( + name="name", + ) + """ + _response = self._raw_client.create( + name=name, + description=description, + metadata=metadata, + input_schema=input_schema, + expected_output_schema=expected_output_schema, + request_options=request_options, + ) + return _response.data + + def get_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DatasetRunWithItems: + """ + Get a dataset run and its items + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetRunWithItems + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.datasets.get_run( + dataset_name="datasetName", + run_name="runName", + ) + """ + _response = self._raw_client.get_run( + dataset_name, run_name, request_options=request_options + ) + return _response.data + + def delete_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteDatasetRunResponse: + """ + Delete a dataset run and all its run items. This action is irreversible. + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteDatasetRunResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.datasets.delete_run( + dataset_name="datasetName", + run_name="runName", + ) + """ + _response = self._raw_client.delete_run( + dataset_name, run_name, request_options=request_options + ) + return _response.data + + def get_runs( + self, + dataset_name: str, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasetRuns: + """ + Get dataset runs + + Parameters + ---------- + dataset_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasetRuns + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.datasets.get_runs( + dataset_name="datasetName", + ) + """ + _response = self._raw_client.get_runs( + dataset_name, page=page, limit=limit, request_options=request_options + ) + return _response.data + + +class AsyncDatasetsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawDatasetsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawDatasetsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawDatasetsClient + """ + return self._raw_client + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasets: + """ + Get all datasets + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasets + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.datasets.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get( + self, + dataset_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> Dataset: + """ + Get a dataset + + Parameters + ---------- + dataset_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Dataset + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.datasets.get( + dataset_name="datasetName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + dataset_name, request_options=request_options + ) + return _response.data + + async def create( + self, + *, + name: str, + description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + input_schema: typing.Optional[typing.Any] = OMIT, + expected_output_schema: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Dataset: + """ + Create a dataset + + Parameters + ---------- + name : str + + description : typing.Optional[str] + + metadata : typing.Optional[typing.Any] + + input_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. + + expected_output_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Dataset + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.datasets.create( + name="name", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + description=description, + metadata=metadata, + input_schema=input_schema, + expected_output_schema=expected_output_schema, + request_options=request_options, + ) + return _response.data + + async def get_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DatasetRunWithItems: + """ + Get a dataset run and its items + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DatasetRunWithItems + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.datasets.get_run( + dataset_name="datasetName", + run_name="runName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_run( + dataset_name, run_name, request_options=request_options + ) + return _response.data + + async def delete_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteDatasetRunResponse: + """ + Delete a dataset run and all its run items. This action is irreversible. + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteDatasetRunResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.datasets.delete_run( + dataset_name="datasetName", + run_name="runName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_run( + dataset_name, run_name, request_options=request_options + ) + return _response.data + + async def get_runs( + self, + dataset_name: str, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedDatasetRuns: + """ + Get dataset runs + + Parameters + ---------- + dataset_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedDatasetRuns + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.datasets.get_runs( + dataset_name="datasetName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_runs( + dataset_name, page=page, limit=limit, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/datasets/raw_client.py b/langfuse/api/datasets/raw_client.py new file mode 100644 index 000000000..306ad8f76 --- /dev/null +++ b/langfuse/api/datasets/raw_client.py @@ -0,0 +1,1368 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.dataset import Dataset +from ..commons.types.dataset_run_with_items import DatasetRunWithItems +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.delete_dataset_run_response import DeleteDatasetRunResponse +from .types.paginated_dataset_runs import PaginatedDatasetRuns +from .types.paginated_datasets import PaginatedDatasets + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawDatasetsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedDatasets]: + """ + Get all datasets + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedDatasets] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/datasets", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasets, + parse_obj_as( + type_=PaginatedDatasets, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, + dataset_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Dataset]: + """ + Get a dataset + + Parameters + ---------- + dataset_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Dataset] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/v2/datasets/{jsonable_encoder(dataset_name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Dataset, + parse_obj_as( + type_=Dataset, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create( + self, + *, + name: str, + description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + input_schema: typing.Optional[typing.Any] = OMIT, + expected_output_schema: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Dataset]: + """ + Create a dataset + + Parameters + ---------- + name : str + + description : typing.Optional[str] + + metadata : typing.Optional[typing.Any] + + input_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. + + expected_output_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Dataset] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/datasets", + method="POST", + json={ + "name": name, + "description": description, + "metadata": metadata, + "inputSchema": input_schema, + "expectedOutputSchema": expected_output_schema, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Dataset, + parse_obj_as( + type_=Dataset, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DatasetRunWithItems]: + """ + Get a dataset run and its items + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DatasetRunWithItems] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetRunWithItems, + parse_obj_as( + type_=DatasetRunWithItems, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DeleteDatasetRunResponse]: + """ + Delete a dataset run and all its run items. This action is irreversible. + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteDatasetRunResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteDatasetRunResponse, + parse_obj_as( + type_=DeleteDatasetRunResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_runs( + self, + dataset_name: str, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedDatasetRuns]: + """ + Get dataset runs + + Parameters + ---------- + dataset_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedDatasetRuns] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasetRuns, + parse_obj_as( + type_=PaginatedDatasetRuns, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawDatasetsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedDatasets]: + """ + Get all datasets + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedDatasets] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/datasets", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasets, + parse_obj_as( + type_=PaginatedDatasets, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, + dataset_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Dataset]: + """ + Get a dataset + + Parameters + ---------- + dataset_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Dataset] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/v2/datasets/{jsonable_encoder(dataset_name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Dataset, + parse_obj_as( + type_=Dataset, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create( + self, + *, + name: str, + description: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Any] = OMIT, + input_schema: typing.Optional[typing.Any] = OMIT, + expected_output_schema: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Dataset]: + """ + Create a dataset + + Parameters + ---------- + name : str + + description : typing.Optional[str] + + metadata : typing.Optional[typing.Any] + + input_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. + + expected_output_schema : typing.Optional[typing.Any] + JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Dataset] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/datasets", + method="POST", + json={ + "name": name, + "description": description, + "metadata": metadata, + "inputSchema": input_schema, + "expectedOutputSchema": expected_output_schema, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Dataset, + parse_obj_as( + type_=Dataset, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DatasetRunWithItems]: + """ + Get a dataset run and its items + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DatasetRunWithItems] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DatasetRunWithItems, + parse_obj_as( + type_=DatasetRunWithItems, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_run( + self, + dataset_name: str, + run_name: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DeleteDatasetRunResponse]: + """ + Delete a dataset run and all its run items. This action is irreversible. + + Parameters + ---------- + dataset_name : str + + run_name : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteDatasetRunResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteDatasetRunResponse, + parse_obj_as( + type_=DeleteDatasetRunResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_runs( + self, + dataset_name: str, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedDatasetRuns]: + """ + Get dataset runs + + Parameters + ---------- + dataset_name : str + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedDatasetRuns] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedDatasetRuns, + parse_obj_as( + type_=PaginatedDatasetRuns, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/datasets/types/__init__.py b/langfuse/api/datasets/types/__init__.py new file mode 100644 index 000000000..60c58934a --- /dev/null +++ b/langfuse/api/datasets/types/__init__.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_dataset_request import CreateDatasetRequest + from .delete_dataset_run_response import DeleteDatasetRunResponse + from .paginated_dataset_runs import PaginatedDatasetRuns + from .paginated_datasets import PaginatedDatasets +_dynamic_imports: typing.Dict[str, str] = { + "CreateDatasetRequest": ".create_dataset_request", + "DeleteDatasetRunResponse": ".delete_dataset_run_response", + "PaginatedDatasetRuns": ".paginated_dataset_runs", + "PaginatedDatasets": ".paginated_datasets", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateDatasetRequest", + "DeleteDatasetRunResponse", + "PaginatedDatasetRuns", + "PaginatedDatasets", +] diff --git a/langfuse/api/datasets/types/create_dataset_request.py b/langfuse/api/datasets/types/create_dataset_request.py new file mode 100644 index 000000000..9d9b01089 --- /dev/null +++ b/langfuse/api/datasets/types/create_dataset_request.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateDatasetRequest(UniversalBaseModel): + name: str + description: typing.Optional[str] = None + metadata: typing.Optional[typing.Any] = None + input_schema: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="inputSchema") + ] = pydantic.Field(default=None) + """ + JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. + """ + + expected_output_schema: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="expectedOutputSchema") + ] = pydantic.Field(default=None) + """ + JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/datasets/types/delete_dataset_run_response.py b/langfuse/api/datasets/types/delete_dataset_run_response.py new file mode 100644 index 000000000..024433ef6 --- /dev/null +++ b/langfuse/api/datasets/types/delete_dataset_run_response.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class DeleteDatasetRunResponse(UniversalBaseModel): + message: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/datasets/types/paginated_dataset_runs.py b/langfuse/api/datasets/types/paginated_dataset_runs.py new file mode 100644 index 000000000..85cd54c55 --- /dev/null +++ b/langfuse/api/datasets/types/paginated_dataset_runs.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.dataset_run import DatasetRun +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class PaginatedDatasetRuns(UniversalBaseModel): + data: typing.List[DatasetRun] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/datasets/types/paginated_datasets.py b/langfuse/api/datasets/types/paginated_datasets.py new file mode 100644 index 000000000..a2e83edf1 --- /dev/null +++ b/langfuse/api/datasets/types/paginated_datasets.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.dataset import Dataset +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class PaginatedDatasets(UniversalBaseModel): + data: typing.List[Dataset] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/health/__init__.py b/langfuse/api/health/__init__.py new file mode 100644 index 000000000..2a101c5f7 --- /dev/null +++ b/langfuse/api/health/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import HealthResponse + from .errors import ServiceUnavailableError +_dynamic_imports: typing.Dict[str, str] = { + "HealthResponse": ".types", + "ServiceUnavailableError": ".errors", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["HealthResponse", "ServiceUnavailableError"] diff --git a/langfuse/api/health/client.py b/langfuse/api/health/client.py new file mode 100644 index 000000000..7406fef6f --- /dev/null +++ b/langfuse/api/health/client.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawHealthClient, RawHealthClient +from .types.health_response import HealthResponse + + +class HealthClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawHealthClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawHealthClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawHealthClient + """ + return self._raw_client + + def health( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HealthResponse: + """ + Check health of API and database + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HealthResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.health.health() + """ + _response = self._raw_client.health(request_options=request_options) + return _response.data + + +class AsyncHealthClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawHealthClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawHealthClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawHealthClient + """ + return self._raw_client + + async def health( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HealthResponse: + """ + Check health of API and database + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HealthResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.health.health() + + + asyncio.run(main()) + """ + _response = await self._raw_client.health(request_options=request_options) + return _response.data diff --git a/langfuse/api/health/errors/__init__.py b/langfuse/api/health/errors/__init__.py new file mode 100644 index 000000000..3567f86b3 --- /dev/null +++ b/langfuse/api/health/errors/__init__.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .service_unavailable_error import ServiceUnavailableError +_dynamic_imports: typing.Dict[str, str] = { + "ServiceUnavailableError": ".service_unavailable_error" +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ServiceUnavailableError"] diff --git a/langfuse/api/health/errors/service_unavailable_error.py b/langfuse/api/health/errors/service_unavailable_error.py new file mode 100644 index 000000000..68d5d836e --- /dev/null +++ b/langfuse/api/health/errors/service_unavailable_error.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class ServiceUnavailableError(ApiError): + def __init__(self, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__( + status_code=503, + headers=headers, + ) diff --git a/langfuse/api/health/raw_client.py b/langfuse/api/health/raw_client.py new file mode 100644 index 000000000..afeef1a96 --- /dev/null +++ b/langfuse/api/health/raw_client.py @@ -0,0 +1,227 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .errors.service_unavailable_error import ServiceUnavailableError +from .types.health_response import HealthResponse + + +class RawHealthClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def health( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[HealthResponse]: + """ + Check health of API and database + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[HealthResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/health", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + HealthResponse, + parse_obj_as( + type_=HealthResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 503: + raise ServiceUnavailableError(headers=dict(_response.headers)) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawHealthClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def health( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[HealthResponse]: + """ + Check health of API and database + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[HealthResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/health", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + HealthResponse, + parse_obj_as( + type_=HealthResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 503: + raise ServiceUnavailableError(headers=dict(_response.headers)) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/health/types/__init__.py b/langfuse/api/health/types/__init__.py new file mode 100644 index 000000000..d4bec6804 --- /dev/null +++ b/langfuse/api/health/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .health_response import HealthResponse +_dynamic_imports: typing.Dict[str, str] = {"HealthResponse": ".health_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["HealthResponse"] diff --git a/langfuse/api/health/types/health_response.py b/langfuse/api/health/types/health_response.py new file mode 100644 index 000000000..a1c7a4bea --- /dev/null +++ b/langfuse/api/health/types/health_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class HealthResponse(UniversalBaseModel): + """ + Examples + -------- + from langfuse.health import HealthResponse + + HealthResponse( + version="1.25.0", + status="OK", + ) + """ + + version: str = pydantic.Field() + """ + Langfuse server version + """ + + status: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/__init__.py b/langfuse/api/ingestion/__init__.py new file mode 100644 index 000000000..5cd4ba3bd --- /dev/null +++ b/langfuse/api/ingestion/__init__.py @@ -0,0 +1,169 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + BaseEvent, + CreateEventBody, + CreateEventEvent, + CreateGenerationBody, + CreateGenerationEvent, + CreateObservationEvent, + CreateSpanBody, + CreateSpanEvent, + IngestionError, + IngestionEvent, + IngestionEvent_EventCreate, + IngestionEvent_GenerationCreate, + IngestionEvent_GenerationUpdate, + IngestionEvent_ObservationCreate, + IngestionEvent_ObservationUpdate, + IngestionEvent_ScoreCreate, + IngestionEvent_SdkLog, + IngestionEvent_SpanCreate, + IngestionEvent_SpanUpdate, + IngestionEvent_TraceCreate, + IngestionResponse, + IngestionSuccess, + IngestionUsage, + ObservationBody, + ObservationType, + OpenAiCompletionUsageSchema, + OpenAiResponseUsageSchema, + OpenAiUsage, + OptionalObservationBody, + ScoreBody, + ScoreEvent, + SdkLogBody, + SdkLogEvent, + TraceBody, + TraceEvent, + UpdateEventBody, + UpdateGenerationBody, + UpdateGenerationEvent, + UpdateObservationEvent, + UpdateSpanBody, + UpdateSpanEvent, + UsageDetails, + ) +_dynamic_imports: typing.Dict[str, str] = { + "BaseEvent": ".types", + "CreateEventBody": ".types", + "CreateEventEvent": ".types", + "CreateGenerationBody": ".types", + "CreateGenerationEvent": ".types", + "CreateObservationEvent": ".types", + "CreateSpanBody": ".types", + "CreateSpanEvent": ".types", + "IngestionError": ".types", + "IngestionEvent": ".types", + "IngestionEvent_EventCreate": ".types", + "IngestionEvent_GenerationCreate": ".types", + "IngestionEvent_GenerationUpdate": ".types", + "IngestionEvent_ObservationCreate": ".types", + "IngestionEvent_ObservationUpdate": ".types", + "IngestionEvent_ScoreCreate": ".types", + "IngestionEvent_SdkLog": ".types", + "IngestionEvent_SpanCreate": ".types", + "IngestionEvent_SpanUpdate": ".types", + "IngestionEvent_TraceCreate": ".types", + "IngestionResponse": ".types", + "IngestionSuccess": ".types", + "IngestionUsage": ".types", + "ObservationBody": ".types", + "ObservationType": ".types", + "OpenAiCompletionUsageSchema": ".types", + "OpenAiResponseUsageSchema": ".types", + "OpenAiUsage": ".types", + "OptionalObservationBody": ".types", + "ScoreBody": ".types", + "ScoreEvent": ".types", + "SdkLogBody": ".types", + "SdkLogEvent": ".types", + "TraceBody": ".types", + "TraceEvent": ".types", + "UpdateEventBody": ".types", + "UpdateGenerationBody": ".types", + "UpdateGenerationEvent": ".types", + "UpdateObservationEvent": ".types", + "UpdateSpanBody": ".types", + "UpdateSpanEvent": ".types", + "UsageDetails": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BaseEvent", + "CreateEventBody", + "CreateEventEvent", + "CreateGenerationBody", + "CreateGenerationEvent", + "CreateObservationEvent", + "CreateSpanBody", + "CreateSpanEvent", + "IngestionError", + "IngestionEvent", + "IngestionEvent_EventCreate", + "IngestionEvent_GenerationCreate", + "IngestionEvent_GenerationUpdate", + "IngestionEvent_ObservationCreate", + "IngestionEvent_ObservationUpdate", + "IngestionEvent_ScoreCreate", + "IngestionEvent_SdkLog", + "IngestionEvent_SpanCreate", + "IngestionEvent_SpanUpdate", + "IngestionEvent_TraceCreate", + "IngestionResponse", + "IngestionSuccess", + "IngestionUsage", + "ObservationBody", + "ObservationType", + "OpenAiCompletionUsageSchema", + "OpenAiResponseUsageSchema", + "OpenAiUsage", + "OptionalObservationBody", + "ScoreBody", + "ScoreEvent", + "SdkLogBody", + "SdkLogEvent", + "TraceBody", + "TraceEvent", + "UpdateEventBody", + "UpdateGenerationBody", + "UpdateGenerationEvent", + "UpdateObservationEvent", + "UpdateSpanBody", + "UpdateSpanEvent", + "UsageDetails", +] diff --git a/langfuse/api/resources/ingestion/client.py b/langfuse/api/ingestion/client.py similarity index 64% rename from langfuse/api/resources/ingestion/client.py rename to langfuse/api/ingestion/client.py index c009c507b..af1da8396 100644 --- a/langfuse/api/resources/ingestion/client.py +++ b/langfuse/api/ingestion/client.py @@ -1,17 +1,10 @@ # This file was auto-generated by Fern from our API Definition. import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawIngestionClient, RawIngestionClient from .types.ingestion_event import IngestionEvent from .types.ingestion_response import IngestionResponse @@ -21,7 +14,18 @@ class IngestionClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = RawIngestionClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawIngestionClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawIngestionClient + """ + return self._raw_client def batch( self, @@ -66,10 +70,10 @@ def batch( -------- import datetime - from langfuse import IngestionEvent_TraceCreate, TraceBody - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI + from langfuse.ingestion import IngestionEvent_TraceCreate, TraceBody - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -103,43 +107,26 @@ def batch( ], ) """ - _response = self._client_wrapper.httpx_client.request( - "api/public/ingestion", - method="POST", - json={"batch": batch, "metadata": metadata}, - request_options=request_options, - omit=OMIT, + _response = self._raw_client.batch( + batch=batch, metadata=metadata, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(IngestionResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data class AsyncIngestionClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = AsyncRawIngestionClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawIngestionClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawIngestionClient + """ + return self._raw_client async def batch( self, @@ -185,10 +172,10 @@ async def batch( import asyncio import datetime - from langfuse import IngestionEvent_TraceCreate, TraceBody - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI + from langfuse.ingestion import IngestionEvent_TraceCreate, TraceBody - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -228,35 +215,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/ingestion", - method="POST", - json={"batch": batch, "metadata": metadata}, - request_options=request_options, - omit=OMIT, + _response = await self._raw_client.batch( + batch=batch, metadata=metadata, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(IngestionResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data diff --git a/langfuse/api/ingestion/raw_client.py b/langfuse/api/ingestion/raw_client.py new file mode 100644 index 000000000..cb60a5fba --- /dev/null +++ b/langfuse/api/ingestion/raw_client.py @@ -0,0 +1,293 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.ingestion_event import IngestionEvent +from .types.ingestion_response import IngestionResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawIngestionClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def batch( + self, + *, + batch: typing.Sequence[IngestionEvent], + metadata: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[IngestionResponse]: + """ + **Legacy endpoint for batch ingestion for Langfuse Observability.** + + -> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). Learn more: https://langfuse.com/integrations/native/opentelemetry + + Within each batch, there can be multiple events. + Each event has a type, an id, a timestamp, metadata and a body. + Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace. + We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. + The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. + I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. + + Notes: + - Introduction to data model: https://langfuse.com/docs/observability/data-model + - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. + - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. + + Parameters + ---------- + batch : typing.Sequence[IngestionEvent] + Batch of tracing events to be ingested. Discriminated by attribute `type`. + + metadata : typing.Optional[typing.Any] + Optional. Metadata field used by the Langfuse SDKs for debugging. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[IngestionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/ingestion", + method="POST", + json={ + "batch": convert_and_respect_annotation_metadata( + object_=batch, + annotation=typing.Sequence[IngestionEvent], + direction="write", + ), + "metadata": metadata, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IngestionResponse, + parse_obj_as( + type_=IngestionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawIngestionClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def batch( + self, + *, + batch: typing.Sequence[IngestionEvent], + metadata: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[IngestionResponse]: + """ + **Legacy endpoint for batch ingestion for Langfuse Observability.** + + -> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). Learn more: https://langfuse.com/integrations/native/opentelemetry + + Within each batch, there can be multiple events. + Each event has a type, an id, a timestamp, metadata and a body. + Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace. + We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. + The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. + I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. + + Notes: + - Introduction to data model: https://langfuse.com/docs/observability/data-model + - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. + - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. + + Parameters + ---------- + batch : typing.Sequence[IngestionEvent] + Batch of tracing events to be ingested. Discriminated by attribute `type`. + + metadata : typing.Optional[typing.Any] + Optional. Metadata field used by the Langfuse SDKs for debugging. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[IngestionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/ingestion", + method="POST", + json={ + "batch": convert_and_respect_annotation_metadata( + object_=batch, + annotation=typing.Sequence[IngestionEvent], + direction="write", + ), + "metadata": metadata, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + IngestionResponse, + parse_obj_as( + type_=IngestionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/ingestion/types/__init__.py b/langfuse/api/ingestion/types/__init__.py new file mode 100644 index 000000000..4addfd9c7 --- /dev/null +++ b/langfuse/api/ingestion/types/__init__.py @@ -0,0 +1,169 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .base_event import BaseEvent + from .create_event_body import CreateEventBody + from .create_event_event import CreateEventEvent + from .create_generation_body import CreateGenerationBody + from .create_generation_event import CreateGenerationEvent + from .create_observation_event import CreateObservationEvent + from .create_span_body import CreateSpanBody + from .create_span_event import CreateSpanEvent + from .ingestion_error import IngestionError + from .ingestion_event import ( + IngestionEvent, + IngestionEvent_EventCreate, + IngestionEvent_GenerationCreate, + IngestionEvent_GenerationUpdate, + IngestionEvent_ObservationCreate, + IngestionEvent_ObservationUpdate, + IngestionEvent_ScoreCreate, + IngestionEvent_SdkLog, + IngestionEvent_SpanCreate, + IngestionEvent_SpanUpdate, + IngestionEvent_TraceCreate, + ) + from .ingestion_response import IngestionResponse + from .ingestion_success import IngestionSuccess + from .ingestion_usage import IngestionUsage + from .observation_body import ObservationBody + from .observation_type import ObservationType + from .open_ai_completion_usage_schema import OpenAiCompletionUsageSchema + from .open_ai_response_usage_schema import OpenAiResponseUsageSchema + from .open_ai_usage import OpenAiUsage + from .optional_observation_body import OptionalObservationBody + from .score_body import ScoreBody + from .score_event import ScoreEvent + from .sdk_log_body import SdkLogBody + from .sdk_log_event import SdkLogEvent + from .trace_body import TraceBody + from .trace_event import TraceEvent + from .update_event_body import UpdateEventBody + from .update_generation_body import UpdateGenerationBody + from .update_generation_event import UpdateGenerationEvent + from .update_observation_event import UpdateObservationEvent + from .update_span_body import UpdateSpanBody + from .update_span_event import UpdateSpanEvent + from .usage_details import UsageDetails +_dynamic_imports: typing.Dict[str, str] = { + "BaseEvent": ".base_event", + "CreateEventBody": ".create_event_body", + "CreateEventEvent": ".create_event_event", + "CreateGenerationBody": ".create_generation_body", + "CreateGenerationEvent": ".create_generation_event", + "CreateObservationEvent": ".create_observation_event", + "CreateSpanBody": ".create_span_body", + "CreateSpanEvent": ".create_span_event", + "IngestionError": ".ingestion_error", + "IngestionEvent": ".ingestion_event", + "IngestionEvent_EventCreate": ".ingestion_event", + "IngestionEvent_GenerationCreate": ".ingestion_event", + "IngestionEvent_GenerationUpdate": ".ingestion_event", + "IngestionEvent_ObservationCreate": ".ingestion_event", + "IngestionEvent_ObservationUpdate": ".ingestion_event", + "IngestionEvent_ScoreCreate": ".ingestion_event", + "IngestionEvent_SdkLog": ".ingestion_event", + "IngestionEvent_SpanCreate": ".ingestion_event", + "IngestionEvent_SpanUpdate": ".ingestion_event", + "IngestionEvent_TraceCreate": ".ingestion_event", + "IngestionResponse": ".ingestion_response", + "IngestionSuccess": ".ingestion_success", + "IngestionUsage": ".ingestion_usage", + "ObservationBody": ".observation_body", + "ObservationType": ".observation_type", + "OpenAiCompletionUsageSchema": ".open_ai_completion_usage_schema", + "OpenAiResponseUsageSchema": ".open_ai_response_usage_schema", + "OpenAiUsage": ".open_ai_usage", + "OptionalObservationBody": ".optional_observation_body", + "ScoreBody": ".score_body", + "ScoreEvent": ".score_event", + "SdkLogBody": ".sdk_log_body", + "SdkLogEvent": ".sdk_log_event", + "TraceBody": ".trace_body", + "TraceEvent": ".trace_event", + "UpdateEventBody": ".update_event_body", + "UpdateGenerationBody": ".update_generation_body", + "UpdateGenerationEvent": ".update_generation_event", + "UpdateObservationEvent": ".update_observation_event", + "UpdateSpanBody": ".update_span_body", + "UpdateSpanEvent": ".update_span_event", + "UsageDetails": ".usage_details", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BaseEvent", + "CreateEventBody", + "CreateEventEvent", + "CreateGenerationBody", + "CreateGenerationEvent", + "CreateObservationEvent", + "CreateSpanBody", + "CreateSpanEvent", + "IngestionError", + "IngestionEvent", + "IngestionEvent_EventCreate", + "IngestionEvent_GenerationCreate", + "IngestionEvent_GenerationUpdate", + "IngestionEvent_ObservationCreate", + "IngestionEvent_ObservationUpdate", + "IngestionEvent_ScoreCreate", + "IngestionEvent_SdkLog", + "IngestionEvent_SpanCreate", + "IngestionEvent_SpanUpdate", + "IngestionEvent_TraceCreate", + "IngestionResponse", + "IngestionSuccess", + "IngestionUsage", + "ObservationBody", + "ObservationType", + "OpenAiCompletionUsageSchema", + "OpenAiResponseUsageSchema", + "OpenAiUsage", + "OptionalObservationBody", + "ScoreBody", + "ScoreEvent", + "SdkLogBody", + "SdkLogEvent", + "TraceBody", + "TraceEvent", + "UpdateEventBody", + "UpdateGenerationBody", + "UpdateGenerationEvent", + "UpdateObservationEvent", + "UpdateSpanBody", + "UpdateSpanEvent", + "UsageDetails", +] diff --git a/langfuse/api/ingestion/types/base_event.py b/langfuse/api/ingestion/types/base_event.py new file mode 100644 index 000000000..70c6bcfa4 --- /dev/null +++ b/langfuse/api/ingestion/types/base_event.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class BaseEvent(UniversalBaseModel): + id: str = pydantic.Field() + """ + UUID v4 that identifies the event + """ + + timestamp: str = pydantic.Field() + """ + Datetime (ISO 8601) of event creation in client. Should be as close to actual event creation in client as possible, this timestamp will be used for ordering of events in future release. Resolution: milliseconds (required), microseconds (optimal). + """ + + metadata: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Optional. Metadata field used by the Langfuse SDKs for debugging. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_event_body.py b/langfuse/api/ingestion/types/create_event_body.py new file mode 100644 index 000000000..0473d8a0d --- /dev/null +++ b/langfuse/api/ingestion/types/create_event_body.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .optional_observation_body import OptionalObservationBody + + +class CreateEventBody(OptionalObservationBody): + id: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_event_event.py b/langfuse/api/ingestion/types/create_event_event.py new file mode 100644 index 000000000..e0cc820e1 --- /dev/null +++ b/langfuse/api/ingestion/types/create_event_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .create_event_body import CreateEventBody + + +class CreateEventEvent(BaseEvent): + body: CreateEventBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_generation_body.py b/langfuse/api/ingestion/types/create_generation_body.py new file mode 100644 index 000000000..72cb57116 --- /dev/null +++ b/langfuse/api/ingestion/types/create_generation_body.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.map_value import MapValue +from ...core.serialization import FieldMetadata +from .create_span_body import CreateSpanBody +from .ingestion_usage import IngestionUsage +from .usage_details import UsageDetails + + +class CreateGenerationBody(CreateSpanBody): + completion_start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completionStartTime") + ] = None + model: typing.Optional[str] = None + model_parameters: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, MapValue]], + FieldMetadata(alias="modelParameters"), + ] = None + usage: typing.Optional[IngestionUsage] = None + usage_details: typing_extensions.Annotated[ + typing.Optional[UsageDetails], FieldMetadata(alias="usageDetails") + ] = None + cost_details: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, float]], FieldMetadata(alias="costDetails") + ] = None + prompt_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="promptName") + ] = None + prompt_version: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="promptVersion") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_generation_event.py b/langfuse/api/ingestion/types/create_generation_event.py new file mode 100644 index 000000000..d62d6cc41 --- /dev/null +++ b/langfuse/api/ingestion/types/create_generation_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .create_generation_body import CreateGenerationBody + + +class CreateGenerationEvent(BaseEvent): + body: CreateGenerationBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_observation_event.py b/langfuse/api/ingestion/types/create_observation_event.py new file mode 100644 index 000000000..06d927f36 --- /dev/null +++ b/langfuse/api/ingestion/types/create_observation_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .observation_body import ObservationBody + + +class CreateObservationEvent(BaseEvent): + body: ObservationBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_span_body.py b/langfuse/api/ingestion/types/create_span_body.py new file mode 100644 index 000000000..7a47d9748 --- /dev/null +++ b/langfuse/api/ingestion/types/create_span_body.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .create_event_body import CreateEventBody + + +class CreateSpanBody(CreateEventBody): + end_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="endTime") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/create_span_event.py b/langfuse/api/ingestion/types/create_span_event.py new file mode 100644 index 000000000..6e60cf1fe --- /dev/null +++ b/langfuse/api/ingestion/types/create_span_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .create_span_body import CreateSpanBody + + +class CreateSpanEvent(BaseEvent): + body: CreateSpanBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/ingestion_error.py b/langfuse/api/ingestion/types/ingestion_error.py new file mode 100644 index 000000000..2391d95eb --- /dev/null +++ b/langfuse/api/ingestion/types/ingestion_error.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class IngestionError(UniversalBaseModel): + id: str + status: int + message: typing.Optional[str] = None + error: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/ingestion_event.py b/langfuse/api/ingestion/types/ingestion_event.py new file mode 100644 index 000000000..03202e635 --- /dev/null +++ b/langfuse/api/ingestion/types/ingestion_event.py @@ -0,0 +1,155 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from .create_event_body import CreateEventBody +from .create_generation_body import CreateGenerationBody +from .create_span_body import CreateSpanBody +from .observation_body import ObservationBody +from .score_body import ScoreBody +from .sdk_log_body import SdkLogBody +from .trace_body import TraceBody +from .update_generation_body import UpdateGenerationBody +from .update_span_body import UpdateSpanBody + + +class IngestionEvent_TraceCreate(UniversalBaseModel): + type: typing.Literal["trace-create"] = "trace-create" + body: TraceBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_ScoreCreate(UniversalBaseModel): + type: typing.Literal["score-create"] = "score-create" + body: ScoreBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_SpanCreate(UniversalBaseModel): + type: typing.Literal["span-create"] = "span-create" + body: CreateSpanBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_SpanUpdate(UniversalBaseModel): + type: typing.Literal["span-update"] = "span-update" + body: UpdateSpanBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_GenerationCreate(UniversalBaseModel): + type: typing.Literal["generation-create"] = "generation-create" + body: CreateGenerationBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_GenerationUpdate(UniversalBaseModel): + type: typing.Literal["generation-update"] = "generation-update" + body: UpdateGenerationBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_EventCreate(UniversalBaseModel): + type: typing.Literal["event-create"] = "event-create" + body: CreateEventBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_SdkLog(UniversalBaseModel): + type: typing.Literal["sdk-log"] = "sdk-log" + body: SdkLogBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_ObservationCreate(UniversalBaseModel): + type: typing.Literal["observation-create"] = "observation-create" + body: ObservationBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class IngestionEvent_ObservationUpdate(UniversalBaseModel): + type: typing.Literal["observation-update"] = "observation-update" + body: ObservationBody + id: str + timestamp: str + metadata: typing.Optional[typing.Any] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +IngestionEvent = typing_extensions.Annotated[ + typing.Union[ + IngestionEvent_TraceCreate, + IngestionEvent_ScoreCreate, + IngestionEvent_SpanCreate, + IngestionEvent_SpanUpdate, + IngestionEvent_GenerationCreate, + IngestionEvent_GenerationUpdate, + IngestionEvent_EventCreate, + IngestionEvent_SdkLog, + IngestionEvent_ObservationCreate, + IngestionEvent_ObservationUpdate, + ], + pydantic.Field(discriminator="type"), +] diff --git a/langfuse/api/ingestion/types/ingestion_response.py b/langfuse/api/ingestion/types/ingestion_response.py new file mode 100644 index 000000000..b9781fab5 --- /dev/null +++ b/langfuse/api/ingestion/types/ingestion_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .ingestion_error import IngestionError +from .ingestion_success import IngestionSuccess + + +class IngestionResponse(UniversalBaseModel): + successes: typing.List[IngestionSuccess] + errors: typing.List[IngestionError] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/ingestion_success.py b/langfuse/api/ingestion/types/ingestion_success.py new file mode 100644 index 000000000..e7894797a --- /dev/null +++ b/langfuse/api/ingestion/types/ingestion_success.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class IngestionSuccess(UniversalBaseModel): + id: str + status: int + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/ingestion/types/ingestion_usage.py b/langfuse/api/ingestion/types/ingestion_usage.py similarity index 100% rename from langfuse/api/resources/ingestion/types/ingestion_usage.py rename to langfuse/api/ingestion/types/ingestion_usage.py diff --git a/langfuse/api/ingestion/types/observation_body.py b/langfuse/api/ingestion/types/observation_body.py new file mode 100644 index 000000000..e989a768f --- /dev/null +++ b/langfuse/api/ingestion/types/observation_body.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.map_value import MapValue +from ...commons.types.observation_level import ObservationLevel +from ...commons.types.usage import Usage +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .observation_type import ObservationType + + +class ObservationBody(UniversalBaseModel): + id: typing.Optional[str] = None + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + type: ObservationType + name: typing.Optional[str] = None + start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="startTime") + ] = None + end_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="endTime") + ] = None + completion_start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completionStartTime") + ] = None + model: typing.Optional[str] = None + model_parameters: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, MapValue]], + FieldMetadata(alias="modelParameters"), + ] = None + input: typing.Optional[typing.Any] = None + version: typing.Optional[str] = None + metadata: typing.Optional[typing.Any] = None + output: typing.Optional[typing.Any] = None + usage: typing.Optional[Usage] = None + level: typing.Optional[ObservationLevel] = None + status_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="statusMessage") + ] = None + parent_observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="parentObservationId") + ] = None + environment: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/ingestion/types/observation_type.py b/langfuse/api/ingestion/types/observation_type.py similarity index 96% rename from langfuse/api/resources/ingestion/types/observation_type.py rename to langfuse/api/ingestion/types/observation_type.py index 2f11300ff..f769e34cf 100644 --- a/langfuse/api/resources/ingestion/types/observation_type.py +++ b/langfuse/api/ingestion/types/observation_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class ObservationType(str, enum.Enum): +class ObservationType(enum.StrEnum): SPAN = "SPAN" GENERATION = "GENERATION" EVENT = "EVENT" diff --git a/langfuse/api/ingestion/types/open_ai_completion_usage_schema.py b/langfuse/api/ingestion/types/open_ai_completion_usage_schema.py new file mode 100644 index 000000000..292c51e79 --- /dev/null +++ b/langfuse/api/ingestion/types/open_ai_completion_usage_schema.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class OpenAiCompletionUsageSchema(UniversalBaseModel): + """ + OpenAI Usage schema from (Chat-)Completion APIs + """ + + prompt_tokens: int + completion_tokens: int + total_tokens: int + prompt_tokens_details: typing.Optional[typing.Dict[str, typing.Optional[int]]] = ( + None + ) + completion_tokens_details: typing.Optional[ + typing.Dict[str, typing.Optional[int]] + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/open_ai_response_usage_schema.py b/langfuse/api/ingestion/types/open_ai_response_usage_schema.py new file mode 100644 index 000000000..93cbc7dba --- /dev/null +++ b/langfuse/api/ingestion/types/open_ai_response_usage_schema.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class OpenAiResponseUsageSchema(UniversalBaseModel): + """ + OpenAI Usage schema from Response API + """ + + input_tokens: int + output_tokens: int + total_tokens: int + input_tokens_details: typing.Optional[typing.Dict[str, typing.Optional[int]]] = None + output_tokens_details: typing.Optional[typing.Dict[str, typing.Optional[int]]] = ( + None + ) + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/open_ai_usage.py b/langfuse/api/ingestion/types/open_ai_usage.py new file mode 100644 index 000000000..7c1ab3160 --- /dev/null +++ b/langfuse/api/ingestion/types/open_ai_usage.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class OpenAiUsage(UniversalBaseModel): + """ + Usage interface of OpenAI for improved compatibility. + """ + + prompt_tokens: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="promptTokens") + ] = None + completion_tokens: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="completionTokens") + ] = None + total_tokens: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="totalTokens") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/optional_observation_body.py b/langfuse/api/ingestion/types/optional_observation_body.py new file mode 100644 index 000000000..f2aaf9b6d --- /dev/null +++ b/langfuse/api/ingestion/types/optional_observation_body.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.observation_level import ObservationLevel +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class OptionalObservationBody(UniversalBaseModel): + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + name: typing.Optional[str] = None + start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="startTime") + ] = None + metadata: typing.Optional[typing.Any] = None + input: typing.Optional[typing.Any] = None + output: typing.Optional[typing.Any] = None + level: typing.Optional[ObservationLevel] = None + status_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="statusMessage") + ] = None + parent_observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="parentObservationId") + ] = None + version: typing.Optional[str] = None + environment: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/score_body.py b/langfuse/api/ingestion/types/score_body.py new file mode 100644 index 000000000..f559187b6 --- /dev/null +++ b/langfuse/api/ingestion/types/score_body.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...commons.types.create_score_value import CreateScoreValue +from ...commons.types.score_data_type import ScoreDataType +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class ScoreBody(UniversalBaseModel): + """ + Examples + -------- + from langfuse.ingestion import ScoreBody + + ScoreBody( + name="novelty", + value=0.9, + trace_id="cdef-1234-5678-90ab", + ) + """ + + id: typing.Optional[str] = None + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str = pydantic.Field() + """ + The name of the score. Always overrides "output" for correction scores. + """ + + environment: typing.Optional[str] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = pydantic.Field(default=None) + """ + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + """ + + value: CreateScoreValue = pydantic.Field() + """ + The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + """ + + comment: typing.Optional[str] = None + metadata: typing.Optional[typing.Any] = None + data_type: typing_extensions.Annotated[ + typing.Optional[ScoreDataType], FieldMetadata(alias="dataType") + ] = pydantic.Field(default=None) + """ + When set, must match the score value's type. If not set, will be inferred from the score value or config + """ + + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = pydantic.Field(default=None) + """ + Reference a score config on a score. When set, the score name must equal the config name and scores must comply with the config's range and data type. For categorical scores, the value must map to a config category. Numeric scores might be constrained by the score config's max and min values + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/score_event.py b/langfuse/api/ingestion/types/score_event.py new file mode 100644 index 000000000..d0d470899 --- /dev/null +++ b/langfuse/api/ingestion/types/score_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .score_body import ScoreBody + + +class ScoreEvent(BaseEvent): + body: ScoreBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/sdk_log_body.py b/langfuse/api/ingestion/types/sdk_log_body.py new file mode 100644 index 000000000..d5b46f118 --- /dev/null +++ b/langfuse/api/ingestion/types/sdk_log_body.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class SdkLogBody(UniversalBaseModel): + log: typing.Any + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/sdk_log_event.py b/langfuse/api/ingestion/types/sdk_log_event.py new file mode 100644 index 000000000..ca303af55 --- /dev/null +++ b/langfuse/api/ingestion/types/sdk_log_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .sdk_log_body import SdkLogBody + + +class SdkLogEvent(BaseEvent): + body: SdkLogBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/trace_body.py b/langfuse/api/ingestion/types/trace_body.py new file mode 100644 index 000000000..7fb2842a0 --- /dev/null +++ b/langfuse/api/ingestion/types/trace_body.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class TraceBody(UniversalBaseModel): + id: typing.Optional[str] = None + timestamp: typing.Optional[dt.datetime] = None + name: typing.Optional[str] = None + user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="userId") + ] = None + input: typing.Optional[typing.Any] = None + output: typing.Optional[typing.Any] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + release: typing.Optional[str] = None + version: typing.Optional[str] = None + metadata: typing.Optional[typing.Any] = None + tags: typing.Optional[typing.List[str]] = None + environment: typing.Optional[str] = None + public: typing.Optional[bool] = pydantic.Field(default=None) + """ + Make trace publicly accessible via url + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/trace_event.py b/langfuse/api/ingestion/types/trace_event.py new file mode 100644 index 000000000..54127597a --- /dev/null +++ b/langfuse/api/ingestion/types/trace_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .trace_body import TraceBody + + +class TraceEvent(BaseEvent): + body: TraceBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/update_event_body.py b/langfuse/api/ingestion/types/update_event_body.py new file mode 100644 index 000000000..055f66f08 --- /dev/null +++ b/langfuse/api/ingestion/types/update_event_body.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .optional_observation_body import OptionalObservationBody + + +class UpdateEventBody(OptionalObservationBody): + id: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/update_generation_body.py b/langfuse/api/ingestion/types/update_generation_body.py new file mode 100644 index 000000000..1d453e759 --- /dev/null +++ b/langfuse/api/ingestion/types/update_generation_body.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.map_value import MapValue +from ...core.serialization import FieldMetadata +from .ingestion_usage import IngestionUsage +from .update_span_body import UpdateSpanBody +from .usage_details import UsageDetails + + +class UpdateGenerationBody(UpdateSpanBody): + completion_start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completionStartTime") + ] = None + model: typing.Optional[str] = None + model_parameters: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, MapValue]], + FieldMetadata(alias="modelParameters"), + ] = None + usage: typing.Optional[IngestionUsage] = None + prompt_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="promptName") + ] = None + usage_details: typing_extensions.Annotated[ + typing.Optional[UsageDetails], FieldMetadata(alias="usageDetails") + ] = None + cost_details: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, float]], FieldMetadata(alias="costDetails") + ] = None + prompt_version: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="promptVersion") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/update_generation_event.py b/langfuse/api/ingestion/types/update_generation_event.py new file mode 100644 index 000000000..e2c7fe284 --- /dev/null +++ b/langfuse/api/ingestion/types/update_generation_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .update_generation_body import UpdateGenerationBody + + +class UpdateGenerationEvent(BaseEvent): + body: UpdateGenerationBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/update_observation_event.py b/langfuse/api/ingestion/types/update_observation_event.py new file mode 100644 index 000000000..5c33e7591 --- /dev/null +++ b/langfuse/api/ingestion/types/update_observation_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .observation_body import ObservationBody + + +class UpdateObservationEvent(BaseEvent): + body: ObservationBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/update_span_body.py b/langfuse/api/ingestion/types/update_span_body.py new file mode 100644 index 000000000..f094b7cdb --- /dev/null +++ b/langfuse/api/ingestion/types/update_span_body.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .update_event_body import UpdateEventBody + + +class UpdateSpanBody(UpdateEventBody): + end_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="endTime") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/update_span_event.py b/langfuse/api/ingestion/types/update_span_event.py new file mode 100644 index 000000000..20214ac9d --- /dev/null +++ b/langfuse/api/ingestion/types/update_span_event.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_event import BaseEvent +from .update_span_body import UpdateSpanBody + + +class UpdateSpanEvent(BaseEvent): + body: UpdateSpanBody + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/ingestion/types/usage_details.py b/langfuse/api/ingestion/types/usage_details.py similarity index 100% rename from langfuse/api/resources/ingestion/types/usage_details.py rename to langfuse/api/ingestion/types/usage_details.py diff --git a/langfuse/api/llm_connections/__init__.py b/langfuse/api/llm_connections/__init__.py new file mode 100644 index 000000000..aba7157f1 --- /dev/null +++ b/langfuse/api/llm_connections/__init__.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + LlmAdapter, + LlmConnection, + PaginatedLlmConnections, + UpsertLlmConnectionRequest, + ) +_dynamic_imports: typing.Dict[str, str] = { + "LlmAdapter": ".types", + "LlmConnection": ".types", + "PaginatedLlmConnections": ".types", + "UpsertLlmConnectionRequest": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "LlmAdapter", + "LlmConnection", + "PaginatedLlmConnections", + "UpsertLlmConnectionRequest", +] diff --git a/langfuse/api/llm_connections/client.py b/langfuse/api/llm_connections/client.py new file mode 100644 index 000000000..213e55e9f --- /dev/null +++ b/langfuse/api/llm_connections/client.py @@ -0,0 +1,311 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawLlmConnectionsClient, RawLlmConnectionsClient +from .types.llm_adapter import LlmAdapter +from .types.llm_connection import LlmConnection +from .types.paginated_llm_connections import PaginatedLlmConnections + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class LlmConnectionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawLlmConnectionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawLlmConnectionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawLlmConnectionsClient + """ + return self._raw_client + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedLlmConnections: + """ + Get all LLM connections in a project + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedLlmConnections + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.llm_connections.list() + """ + _response = self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def upsert( + self, + *, + provider: str, + adapter: LlmAdapter, + secret_key: str, + base_url: typing.Optional[str] = OMIT, + custom_models: typing.Optional[typing.Sequence[str]] = OMIT, + with_default_models: typing.Optional[bool] = OMIT, + extra_headers: typing.Optional[typing.Dict[str, str]] = OMIT, + config: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> LlmConnection: + """ + Create or update an LLM connection. The connection is upserted on provider. + + Parameters + ---------- + provider : str + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + + adapter : LlmAdapter + The adapter used to interface with the LLM + + secret_key : str + Secret key for the LLM API. + + base_url : typing.Optional[str] + Custom base URL for the LLM API + + custom_models : typing.Optional[typing.Sequence[str]] + List of custom model names + + with_default_models : typing.Optional[bool] + Whether to include default models. Default is true. + + extra_headers : typing.Optional[typing.Dict[str, str]] + Extra headers to send with requests + + config : typing.Optional[typing.Dict[str, typing.Any]] + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + LlmConnection + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.llm_connections import LlmAdapter + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.llm_connections.upsert( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ) + """ + _response = self._raw_client.upsert( + provider=provider, + adapter=adapter, + secret_key=secret_key, + base_url=base_url, + custom_models=custom_models, + with_default_models=with_default_models, + extra_headers=extra_headers, + config=config, + request_options=request_options, + ) + return _response.data + + +class AsyncLlmConnectionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawLlmConnectionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawLlmConnectionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawLlmConnectionsClient + """ + return self._raw_client + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedLlmConnections: + """ + Get all LLM connections in a project + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedLlmConnections + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.llm_connections.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def upsert( + self, + *, + provider: str, + adapter: LlmAdapter, + secret_key: str, + base_url: typing.Optional[str] = OMIT, + custom_models: typing.Optional[typing.Sequence[str]] = OMIT, + with_default_models: typing.Optional[bool] = OMIT, + extra_headers: typing.Optional[typing.Dict[str, str]] = OMIT, + config: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> LlmConnection: + """ + Create or update an LLM connection. The connection is upserted on provider. + + Parameters + ---------- + provider : str + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + + adapter : LlmAdapter + The adapter used to interface with the LLM + + secret_key : str + Secret key for the LLM API. + + base_url : typing.Optional[str] + Custom base URL for the LLM API + + custom_models : typing.Optional[typing.Sequence[str]] + List of custom model names + + with_default_models : typing.Optional[bool] + Whether to include default models. Default is true. + + extra_headers : typing.Optional[typing.Dict[str, str]] + Extra headers to send with requests + + config : typing.Optional[typing.Dict[str, typing.Any]] + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + LlmConnection + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.llm_connections import LlmAdapter + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.llm_connections.upsert( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.upsert( + provider=provider, + adapter=adapter, + secret_key=secret_key, + base_url=base_url, + custom_models=custom_models, + with_default_models=with_default_models, + extra_headers=extra_headers, + config=config, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/llm_connections/raw_client.py b/langfuse/api/llm_connections/raw_client.py new file mode 100644 index 000000000..ef4f87425 --- /dev/null +++ b/langfuse/api/llm_connections/raw_client.py @@ -0,0 +1,541 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.llm_adapter import LlmAdapter +from .types.llm_connection import LlmConnection +from .types.paginated_llm_connections import PaginatedLlmConnections + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawLlmConnectionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedLlmConnections]: + """ + Get all LLM connections in a project + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedLlmConnections] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedLlmConnections, + parse_obj_as( + type_=PaginatedLlmConnections, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def upsert( + self, + *, + provider: str, + adapter: LlmAdapter, + secret_key: str, + base_url: typing.Optional[str] = OMIT, + custom_models: typing.Optional[typing.Sequence[str]] = OMIT, + with_default_models: typing.Optional[bool] = OMIT, + extra_headers: typing.Optional[typing.Dict[str, str]] = OMIT, + config: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[LlmConnection]: + """ + Create or update an LLM connection. The connection is upserted on provider. + + Parameters + ---------- + provider : str + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + + adapter : LlmAdapter + The adapter used to interface with the LLM + + secret_key : str + Secret key for the LLM API. + + base_url : typing.Optional[str] + Custom base URL for the LLM API + + custom_models : typing.Optional[typing.Sequence[str]] + List of custom model names + + with_default_models : typing.Optional[bool] + Whether to include default models. Default is true. + + extra_headers : typing.Optional[typing.Dict[str, str]] + Extra headers to send with requests + + config : typing.Optional[typing.Dict[str, typing.Any]] + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[LlmConnection] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="PUT", + json={ + "provider": provider, + "adapter": adapter, + "secretKey": secret_key, + "baseURL": base_url, + "customModels": custom_models, + "withDefaultModels": with_default_models, + "extraHeaders": extra_headers, + "config": config, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + LlmConnection, + parse_obj_as( + type_=LlmConnection, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawLlmConnectionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedLlmConnections]: + """ + Get all LLM connections in a project + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedLlmConnections] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedLlmConnections, + parse_obj_as( + type_=PaginatedLlmConnections, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def upsert( + self, + *, + provider: str, + adapter: LlmAdapter, + secret_key: str, + base_url: typing.Optional[str] = OMIT, + custom_models: typing.Optional[typing.Sequence[str]] = OMIT, + with_default_models: typing.Optional[bool] = OMIT, + extra_headers: typing.Optional[typing.Dict[str, str]] = OMIT, + config: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[LlmConnection]: + """ + Create or update an LLM connection. The connection is upserted on provider. + + Parameters + ---------- + provider : str + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + + adapter : LlmAdapter + The adapter used to interface with the LLM + + secret_key : str + Secret key for the LLM API. + + base_url : typing.Optional[str] + Custom base URL for the LLM API + + custom_models : typing.Optional[typing.Sequence[str]] + List of custom model names + + with_default_models : typing.Optional[bool] + Whether to include default models. Default is true. + + extra_headers : typing.Optional[typing.Dict[str, str]] + Extra headers to send with requests + + config : typing.Optional[typing.Dict[str, typing.Any]] + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[LlmConnection] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/llm-connections", + method="PUT", + json={ + "provider": provider, + "adapter": adapter, + "secretKey": secret_key, + "baseURL": base_url, + "customModels": custom_models, + "withDefaultModels": with_default_models, + "extraHeaders": extra_headers, + "config": config, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + LlmConnection, + parse_obj_as( + type_=LlmConnection, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/llm_connections/types/__init__.py b/langfuse/api/llm_connections/types/__init__.py new file mode 100644 index 000000000..e6ba89200 --- /dev/null +++ b/langfuse/api/llm_connections/types/__init__.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .llm_adapter import LlmAdapter + from .llm_connection import LlmConnection + from .paginated_llm_connections import PaginatedLlmConnections + from .upsert_llm_connection_request import UpsertLlmConnectionRequest +_dynamic_imports: typing.Dict[str, str] = { + "LlmAdapter": ".llm_adapter", + "LlmConnection": ".llm_connection", + "PaginatedLlmConnections": ".paginated_llm_connections", + "UpsertLlmConnectionRequest": ".upsert_llm_connection_request", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "LlmAdapter", + "LlmConnection", + "PaginatedLlmConnections", + "UpsertLlmConnectionRequest", +] diff --git a/langfuse/api/resources/llm_connections/types/llm_adapter.py b/langfuse/api/llm_connections/types/llm_adapter.py similarity index 94% rename from langfuse/api/resources/llm_connections/types/llm_adapter.py rename to langfuse/api/llm_connections/types/llm_adapter.py index d03513aeb..08cab5cb9 100644 --- a/langfuse/api/resources/llm_connections/types/llm_adapter.py +++ b/langfuse/api/llm_connections/types/llm_adapter.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class LlmAdapter(str, enum.Enum): +class LlmAdapter(enum.StrEnum): ANTHROPIC = "anthropic" OPEN_AI = "openai" AZURE = "azure" diff --git a/langfuse/api/llm_connections/types/llm_connection.py b/langfuse/api/llm_connections/types/llm_connection.py new file mode 100644 index 000000000..f74ff98c2 --- /dev/null +++ b/langfuse/api/llm_connections/types/llm_connection.py @@ -0,0 +1,77 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class LlmConnection(UniversalBaseModel): + """ + LLM API connection configuration (secrets excluded) + """ + + id: str + provider: str = pydantic.Field() + """ + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + """ + + adapter: str = pydantic.Field() + """ + The adapter used to interface with the LLM + """ + + display_secret_key: typing_extensions.Annotated[ + str, FieldMetadata(alias="displaySecretKey") + ] = pydantic.Field() + """ + Masked version of the secret key for display purposes + """ + + base_url: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="baseURL") + ] = pydantic.Field(default=None) + """ + Custom base URL for the LLM API + """ + + custom_models: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="customModels") + ] = pydantic.Field() + """ + List of custom model names available for this connection + """ + + with_default_models: typing_extensions.Annotated[ + bool, FieldMetadata(alias="withDefaultModels") + ] = pydantic.Field() + """ + Whether to include default models for this adapter + """ + + extra_header_keys: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="extraHeaderKeys") + ] = pydantic.Field() + """ + Keys of extra headers sent with requests (values excluded for security) + """ + + config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Adapter-specific configuration. Required for Bedrock (`{"region":"us-east-1"}`), optional for VertexAI (`{"location":"us-central1"}`), not used by other adapters. + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/llm_connections/types/paginated_llm_connections.py b/langfuse/api/llm_connections/types/paginated_llm_connections.py new file mode 100644 index 000000000..5d8ce52af --- /dev/null +++ b/langfuse/api/llm_connections/types/paginated_llm_connections.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse +from .llm_connection import LlmConnection + + +class PaginatedLlmConnections(UniversalBaseModel): + data: typing.List[LlmConnection] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/llm_connections/types/upsert_llm_connection_request.py b/langfuse/api/llm_connections/types/upsert_llm_connection_request.py new file mode 100644 index 000000000..712362fa1 --- /dev/null +++ b/langfuse/api/llm_connections/types/upsert_llm_connection_request.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .llm_adapter import LlmAdapter + + +class UpsertLlmConnectionRequest(UniversalBaseModel): + """ + Request to create or update an LLM connection (upsert) + """ + + provider: str = pydantic.Field() + """ + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. + """ + + adapter: LlmAdapter = pydantic.Field() + """ + The adapter used to interface with the LLM + """ + + secret_key: typing_extensions.Annotated[str, FieldMetadata(alias="secretKey")] = ( + pydantic.Field() + ) + """ + Secret key for the LLM API. + """ + + base_url: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="baseURL") + ] = pydantic.Field(default=None) + """ + Custom base URL for the LLM API + """ + + custom_models: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="customModels") + ] = pydantic.Field(default=None) + """ + List of custom model names + """ + + with_default_models: typing_extensions.Annotated[ + typing.Optional[bool], FieldMetadata(alias="withDefaultModels") + ] = pydantic.Field(default=None) + """ + Whether to include default models. Default is true. + """ + + extra_headers: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, str]], FieldMetadata(alias="extraHeaders") + ] = pydantic.Field(default=None) + """ + Extra headers to send with requests + """ + + config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/media/__init__.py b/langfuse/api/media/__init__.py new file mode 100644 index 000000000..85d8f7b4f --- /dev/null +++ b/langfuse/api/media/__init__.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + GetMediaResponse, + GetMediaUploadUrlRequest, + GetMediaUploadUrlResponse, + MediaContentType, + PatchMediaBody, + ) +_dynamic_imports: typing.Dict[str, str] = { + "GetMediaResponse": ".types", + "GetMediaUploadUrlRequest": ".types", + "GetMediaUploadUrlResponse": ".types", + "MediaContentType": ".types", + "PatchMediaBody": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GetMediaResponse", + "GetMediaUploadUrlRequest", + "GetMediaUploadUrlResponse", + "MediaContentType", + "PatchMediaBody", +] diff --git a/langfuse/api/media/client.py b/langfuse/api/media/client.py new file mode 100644 index 000000000..b22272b92 --- /dev/null +++ b/langfuse/api/media/client.py @@ -0,0 +1,427 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawMediaClient, RawMediaClient +from .types.get_media_response import GetMediaResponse +from .types.get_media_upload_url_response import GetMediaUploadUrlResponse +from .types.media_content_type import MediaContentType + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class MediaClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMediaClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMediaClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMediaClient + """ + return self._raw_client + + def get( + self, media_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> GetMediaResponse: + """ + Get a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetMediaResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.media.get( + media_id="mediaId", + ) + """ + _response = self._raw_client.get(media_id, request_options=request_options) + return _response.data + + def patch( + self, + media_id: str, + *, + uploaded_at: dt.datetime, + upload_http_status: int, + upload_http_error: typing.Optional[str] = OMIT, + upload_time_ms: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: + """ + Patch a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + uploaded_at : dt.datetime + The date and time when the media record was uploaded + + upload_http_status : int + The HTTP status code of the upload + + upload_http_error : typing.Optional[str] + The HTTP error message of the upload + + upload_time_ms : typing.Optional[int] + The time in milliseconds it took to upload the media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import datetime + + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.media.patch( + media_id="mediaId", + uploaded_at=datetime.datetime.fromisoformat( + "2024-01-15 09:30:00+00:00", + ), + upload_http_status=1, + ) + """ + _response = self._raw_client.patch( + media_id, + uploaded_at=uploaded_at, + upload_http_status=upload_http_status, + upload_http_error=upload_http_error, + upload_time_ms=upload_time_ms, + request_options=request_options, + ) + return _response.data + + def get_upload_url( + self, + *, + trace_id: str, + content_type: MediaContentType, + content_length: int, + sha256hash: str, + field: str, + observation_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetMediaUploadUrlResponse: + """ + Get a presigned upload URL for a media record + + Parameters + ---------- + trace_id : str + The trace ID associated with the media record + + content_type : MediaContentType + + content_length : int + The size of the media record in bytes + + sha256hash : str + The SHA-256 hash of the media record + + field : str + The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` + + observation_id : typing.Optional[str] + The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetMediaUploadUrlResponse + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.media import MediaContentType + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.media.get_upload_url( + trace_id="traceId", + content_type=MediaContentType.IMAGE_PNG, + content_length=1, + sha256hash="sha256Hash", + field="field", + ) + """ + _response = self._raw_client.get_upload_url( + trace_id=trace_id, + content_type=content_type, + content_length=content_length, + sha256hash=sha256hash, + field=field, + observation_id=observation_id, + request_options=request_options, + ) + return _response.data + + +class AsyncMediaClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMediaClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMediaClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMediaClient + """ + return self._raw_client + + async def get( + self, media_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> GetMediaResponse: + """ + Get a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetMediaResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.media.get( + media_id="mediaId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + media_id, request_options=request_options + ) + return _response.data + + async def patch( + self, + media_id: str, + *, + uploaded_at: dt.datetime, + upload_http_status: int, + upload_http_error: typing.Optional[str] = OMIT, + upload_time_ms: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: + """ + Patch a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + uploaded_at : dt.datetime + The date and time when the media record was uploaded + + upload_http_status : int + The HTTP status code of the upload + + upload_http_error : typing.Optional[str] + The HTTP error message of the upload + + upload_time_ms : typing.Optional[int] + The time in milliseconds it took to upload the media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + import datetime + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.media.patch( + media_id="mediaId", + uploaded_at=datetime.datetime.fromisoformat( + "2024-01-15 09:30:00+00:00", + ), + upload_http_status=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.patch( + media_id, + uploaded_at=uploaded_at, + upload_http_status=upload_http_status, + upload_http_error=upload_http_error, + upload_time_ms=upload_time_ms, + request_options=request_options, + ) + return _response.data + + async def get_upload_url( + self, + *, + trace_id: str, + content_type: MediaContentType, + content_length: int, + sha256hash: str, + field: str, + observation_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetMediaUploadUrlResponse: + """ + Get a presigned upload URL for a media record + + Parameters + ---------- + trace_id : str + The trace ID associated with the media record + + content_type : MediaContentType + + content_length : int + The size of the media record in bytes + + sha256hash : str + The SHA-256 hash of the media record + + field : str + The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` + + observation_id : typing.Optional[str] + The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetMediaUploadUrlResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.media import MediaContentType + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.media.get_upload_url( + trace_id="traceId", + content_type=MediaContentType.IMAGE_PNG, + content_length=1, + sha256hash="sha256Hash", + field="field", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_upload_url( + trace_id=trace_id, + content_type=content_type, + content_length=content_length, + sha256hash=sha256hash, + field=field, + observation_id=observation_id, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/media/raw_client.py b/langfuse/api/media/raw_client.py new file mode 100644 index 000000000..4cc619770 --- /dev/null +++ b/langfuse/api/media/raw_client.py @@ -0,0 +1,739 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.get_media_response import GetMediaResponse +from .types.get_media_upload_url_response import GetMediaUploadUrlResponse +from .types.media_content_type import MediaContentType + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawMediaClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, media_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[GetMediaResponse]: + """ + Get a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetMediaResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/media/{jsonable_encoder(media_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetMediaResponse, + parse_obj_as( + type_=GetMediaResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def patch( + self, + media_id: str, + *, + uploaded_at: dt.datetime, + upload_http_status: int, + upload_http_error: typing.Optional[str] = OMIT, + upload_time_ms: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[None]: + """ + Patch a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + uploaded_at : dt.datetime + The date and time when the media record was uploaded + + upload_http_status : int + The HTTP status code of the upload + + upload_http_error : typing.Optional[str] + The HTTP error message of the upload + + upload_time_ms : typing.Optional[int] + The time in milliseconds it took to upload the media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/media/{jsonable_encoder(media_id)}", + method="PATCH", + json={ + "uploadedAt": uploaded_at, + "uploadHttpStatus": upload_http_status, + "uploadHttpError": upload_http_error, + "uploadTimeMs": upload_time_ms, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_upload_url( + self, + *, + trace_id: str, + content_type: MediaContentType, + content_length: int, + sha256hash: str, + field: str, + observation_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GetMediaUploadUrlResponse]: + """ + Get a presigned upload URL for a media record + + Parameters + ---------- + trace_id : str + The trace ID associated with the media record + + content_type : MediaContentType + + content_length : int + The size of the media record in bytes + + sha256hash : str + The SHA-256 hash of the media record + + field : str + The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` + + observation_id : typing.Optional[str] + The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetMediaUploadUrlResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/media", + method="POST", + json={ + "traceId": trace_id, + "observationId": observation_id, + "contentType": content_type, + "contentLength": content_length, + "sha256Hash": sha256hash, + "field": field, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetMediaUploadUrlResponse, + parse_obj_as( + type_=GetMediaUploadUrlResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawMediaClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, media_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetMediaResponse]: + """ + Get a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetMediaResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/media/{jsonable_encoder(media_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetMediaResponse, + parse_obj_as( + type_=GetMediaResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def patch( + self, + media_id: str, + *, + uploaded_at: dt.datetime, + upload_http_status: int, + upload_http_error: typing.Optional[str] = OMIT, + upload_time_ms: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[None]: + """ + Patch a media record + + Parameters + ---------- + media_id : str + The unique langfuse identifier of a media record + + uploaded_at : dt.datetime + The date and time when the media record was uploaded + + upload_http_status : int + The HTTP status code of the upload + + upload_http_error : typing.Optional[str] + The HTTP error message of the upload + + upload_time_ms : typing.Optional[int] + The time in milliseconds it took to upload the media record + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/media/{jsonable_encoder(media_id)}", + method="PATCH", + json={ + "uploadedAt": uploaded_at, + "uploadHttpStatus": upload_http_status, + "uploadHttpError": upload_http_error, + "uploadTimeMs": upload_time_ms, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_upload_url( + self, + *, + trace_id: str, + content_type: MediaContentType, + content_length: int, + sha256hash: str, + field: str, + observation_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GetMediaUploadUrlResponse]: + """ + Get a presigned upload URL for a media record + + Parameters + ---------- + trace_id : str + The trace ID associated with the media record + + content_type : MediaContentType + + content_length : int + The size of the media record in bytes + + sha256hash : str + The SHA-256 hash of the media record + + field : str + The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` + + observation_id : typing.Optional[str] + The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetMediaUploadUrlResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/media", + method="POST", + json={ + "traceId": trace_id, + "observationId": observation_id, + "contentType": content_type, + "contentLength": content_length, + "sha256Hash": sha256hash, + "field": field, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetMediaUploadUrlResponse, + parse_obj_as( + type_=GetMediaUploadUrlResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/media/types/__init__.py b/langfuse/api/media/types/__init__.py new file mode 100644 index 000000000..0fb9a44ed --- /dev/null +++ b/langfuse/api/media/types/__init__.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .get_media_response import GetMediaResponse + from .get_media_upload_url_request import GetMediaUploadUrlRequest + from .get_media_upload_url_response import GetMediaUploadUrlResponse + from .media_content_type import MediaContentType + from .patch_media_body import PatchMediaBody +_dynamic_imports: typing.Dict[str, str] = { + "GetMediaResponse": ".get_media_response", + "GetMediaUploadUrlRequest": ".get_media_upload_url_request", + "GetMediaUploadUrlResponse": ".get_media_upload_url_response", + "MediaContentType": ".media_content_type", + "PatchMediaBody": ".patch_media_body", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GetMediaResponse", + "GetMediaUploadUrlRequest", + "GetMediaUploadUrlResponse", + "MediaContentType", + "PatchMediaBody", +] diff --git a/langfuse/api/media/types/get_media_response.py b/langfuse/api/media/types/get_media_response.py new file mode 100644 index 000000000..fc1f70329 --- /dev/null +++ b/langfuse/api/media/types/get_media_response.py @@ -0,0 +1,55 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class GetMediaResponse(UniversalBaseModel): + media_id: typing_extensions.Annotated[str, FieldMetadata(alias="mediaId")] = ( + pydantic.Field() + ) + """ + The unique langfuse identifier of a media record + """ + + content_type: typing_extensions.Annotated[ + str, FieldMetadata(alias="contentType") + ] = pydantic.Field() + """ + The MIME type of the media record + """ + + content_length: typing_extensions.Annotated[ + int, FieldMetadata(alias="contentLength") + ] = pydantic.Field() + """ + The size of the media record in bytes + """ + + uploaded_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="uploadedAt") + ] = pydantic.Field() + """ + The date and time when the media record was uploaded + """ + + url: str = pydantic.Field() + """ + The download URL of the media record + """ + + url_expiry: typing_extensions.Annotated[str, FieldMetadata(alias="urlExpiry")] = ( + pydantic.Field() + ) + """ + The expiry date and time of the media record download URL + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/media/types/get_media_upload_url_request.py b/langfuse/api/media/types/get_media_upload_url_request.py new file mode 100644 index 000000000..99f055847 --- /dev/null +++ b/langfuse/api/media/types/get_media_upload_url_request.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .media_content_type import MediaContentType + + +class GetMediaUploadUrlRequest(UniversalBaseModel): + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] = ( + pydantic.Field() + ) + """ + The trace ID associated with the media record + """ + + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = pydantic.Field(default=None) + """ + The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. + """ + + content_type: typing_extensions.Annotated[ + MediaContentType, FieldMetadata(alias="contentType") + ] + content_length: typing_extensions.Annotated[ + int, FieldMetadata(alias="contentLength") + ] = pydantic.Field() + """ + The size of the media record in bytes + """ + + sha256hash: typing_extensions.Annotated[str, FieldMetadata(alias="sha256Hash")] = ( + pydantic.Field() + ) + """ + The SHA-256 hash of the media record + """ + + field: str = pydantic.Field() + """ + The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/media/types/get_media_upload_url_response.py b/langfuse/api/media/types/get_media_upload_url_response.py new file mode 100644 index 000000000..90c735be3 --- /dev/null +++ b/langfuse/api/media/types/get_media_upload_url_response.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class GetMediaUploadUrlResponse(UniversalBaseModel): + upload_url: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="uploadUrl") + ] = pydantic.Field(default=None) + """ + The presigned upload URL. If the asset is already uploaded, this will be null + """ + + media_id: typing_extensions.Annotated[str, FieldMetadata(alias="mediaId")] = ( + pydantic.Field() + ) + """ + The unique langfuse identifier of a media record + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/media/types/media_content_type.py b/langfuse/api/media/types/media_content_type.py similarity index 92% rename from langfuse/api/resources/media/types/media_content_type.py rename to langfuse/api/media/types/media_content_type.py index 6942482bb..9fb5507bc 100644 --- a/langfuse/api/resources/media/types/media_content_type.py +++ b/langfuse/api/media/types/media_content_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class MediaContentType(str, enum.Enum): +class MediaContentType(enum.StrEnum): """ The MIME type of the media record """ @@ -22,16 +23,16 @@ class MediaContentType(str, enum.Enum): IMAGE_AVIF = "image/avif" IMAGE_HEIC = "image/heic" AUDIO_MPEG = "audio/mpeg" - AUDIO_MP_3 = "audio/mp3" + AUDIO_MP3 = "audio/mp3" AUDIO_WAV = "audio/wav" AUDIO_OGG = "audio/ogg" AUDIO_OGA = "audio/oga" AUDIO_AAC = "audio/aac" - AUDIO_MP_4 = "audio/mp4" + AUDIO_MP4 = "audio/mp4" AUDIO_FLAC = "audio/flac" AUDIO_OPUS = "audio/opus" AUDIO_WEBM = "audio/webm" - VIDEO_MP_4 = "video/mp4" + VIDEO_MP4 = "video/mp4" VIDEO_WEBM = "video/webm" VIDEO_OGG = "video/ogg" VIDEO_MPEG = "video/mpeg" @@ -68,7 +69,7 @@ class MediaContentType(str, enum.Enum): APPLICATION_PARQUET = "application/vnd.apache.parquet" APPLICATION_GZIP = "application/gzip" APPLICATION_X_TAR = "application/x-tar" - APPLICATION_X_7_Z_COMPRESSED = "application/x-7z-compressed" + APPLICATION_X7Z_COMPRESSED = "application/x-7z-compressed" def visit( self, @@ -83,16 +84,16 @@ def visit( image_avif: typing.Callable[[], T_Result], image_heic: typing.Callable[[], T_Result], audio_mpeg: typing.Callable[[], T_Result], - audio_mp_3: typing.Callable[[], T_Result], + audio_mp3: typing.Callable[[], T_Result], audio_wav: typing.Callable[[], T_Result], audio_ogg: typing.Callable[[], T_Result], audio_oga: typing.Callable[[], T_Result], audio_aac: typing.Callable[[], T_Result], - audio_mp_4: typing.Callable[[], T_Result], + audio_mp4: typing.Callable[[], T_Result], audio_flac: typing.Callable[[], T_Result], audio_opus: typing.Callable[[], T_Result], audio_webm: typing.Callable[[], T_Result], - video_mp_4: typing.Callable[[], T_Result], + video_mp4: typing.Callable[[], T_Result], video_webm: typing.Callable[[], T_Result], video_ogg: typing.Callable[[], T_Result], video_mpeg: typing.Callable[[], T_Result], @@ -123,7 +124,7 @@ def visit( application_parquet: typing.Callable[[], T_Result], application_gzip: typing.Callable[[], T_Result], application_x_tar: typing.Callable[[], T_Result], - application_x_7_z_compressed: typing.Callable[[], T_Result], + application_x7z_compressed: typing.Callable[[], T_Result], ) -> T_Result: if self is MediaContentType.IMAGE_PNG: return image_png() @@ -147,8 +148,8 @@ def visit( return image_heic() if self is MediaContentType.AUDIO_MPEG: return audio_mpeg() - if self is MediaContentType.AUDIO_MP_3: - return audio_mp_3() + if self is MediaContentType.AUDIO_MP3: + return audio_mp3() if self is MediaContentType.AUDIO_WAV: return audio_wav() if self is MediaContentType.AUDIO_OGG: @@ -157,16 +158,16 @@ def visit( return audio_oga() if self is MediaContentType.AUDIO_AAC: return audio_aac() - if self is MediaContentType.AUDIO_MP_4: - return audio_mp_4() + if self is MediaContentType.AUDIO_MP4: + return audio_mp4() if self is MediaContentType.AUDIO_FLAC: return audio_flac() if self is MediaContentType.AUDIO_OPUS: return audio_opus() if self is MediaContentType.AUDIO_WEBM: return audio_webm() - if self is MediaContentType.VIDEO_MP_4: - return video_mp_4() + if self is MediaContentType.VIDEO_MP4: + return video_mp4() if self is MediaContentType.VIDEO_WEBM: return video_webm() if self is MediaContentType.VIDEO_OGG: @@ -227,5 +228,5 @@ def visit( return application_gzip() if self is MediaContentType.APPLICATION_X_TAR: return application_x_tar() - if self is MediaContentType.APPLICATION_X_7_Z_COMPRESSED: - return application_x_7_z_compressed() + if self is MediaContentType.APPLICATION_X7Z_COMPRESSED: + return application_x7z_compressed() diff --git a/langfuse/api/media/types/patch_media_body.py b/langfuse/api/media/types/patch_media_body.py new file mode 100644 index 000000000..e5f93f601 --- /dev/null +++ b/langfuse/api/media/types/patch_media_body.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class PatchMediaBody(UniversalBaseModel): + uploaded_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="uploadedAt") + ] = pydantic.Field() + """ + The date and time when the media record was uploaded + """ + + upload_http_status: typing_extensions.Annotated[ + int, FieldMetadata(alias="uploadHttpStatus") + ] = pydantic.Field() + """ + The HTTP status code of the upload + """ + + upload_http_error: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="uploadHttpError") + ] = pydantic.Field(default=None) + """ + The HTTP error message of the upload + """ + + upload_time_ms: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="uploadTimeMs") + ] = pydantic.Field(default=None) + """ + The time in milliseconds it took to upload the media record + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/metrics/__init__.py b/langfuse/api/metrics/__init__.py new file mode 100644 index 000000000..fb47bc976 --- /dev/null +++ b/langfuse/api/metrics/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import MetricsResponse +_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetricsResponse"] diff --git a/langfuse/api/resources/metrics/client.py b/langfuse/api/metrics/client.py similarity index 65% rename from langfuse/api/resources/metrics/client.py rename to langfuse/api/metrics/client.py index 098c520c7..a7a3fe359 100644 --- a/langfuse/api/resources/metrics/client.py +++ b/langfuse/api/metrics/client.py @@ -1,23 +1,27 @@ # This file was auto-generated by Fern from our API Definition. import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawMetricsClient, RawMetricsClient from .types.metrics_response import MetricsResponse class MetricsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = RawMetricsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMetricsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMetricsClient + """ + return self._raw_client def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None @@ -83,9 +87,9 @@ def metrics( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -97,42 +101,26 @@ def metrics( query="query", ) """ - _response = self._client_wrapper.httpx_client.request( - "api/public/metrics", - method="GET", - params={"query": query}, - request_options=request_options, + _response = self._raw_client.metrics( + query=query, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MetricsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data class AsyncMetricsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = AsyncRawMetricsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMetricsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMetricsClient + """ + return self._raw_client async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None @@ -200,9 +188,9 @@ async def metrics( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -220,34 +208,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/metrics", - method="GET", - params={"query": query}, - request_options=request_options, + _response = await self._raw_client.metrics( + query=query, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MetricsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data diff --git a/langfuse/api/metrics/raw_client.py b/langfuse/api/metrics/raw_client.py new file mode 100644 index 000000000..605b095db --- /dev/null +++ b/langfuse/api/metrics/raw_client.py @@ -0,0 +1,322 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.metrics_response import MetricsResponse + + +class RawMetricsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MetricsResponse]: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MetricsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsResponse, + parse_obj_as( + type_=MetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawMetricsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MetricsResponse]: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MetricsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsResponse, + parse_obj_as( + type_=MetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/metrics/types/__init__.py b/langfuse/api/metrics/types/__init__.py new file mode 100644 index 000000000..308847504 --- /dev/null +++ b/langfuse/api/metrics/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .metrics_response import MetricsResponse +_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".metrics_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetricsResponse"] diff --git a/langfuse/api/metrics/types/metrics_response.py b/langfuse/api/metrics/types/metrics_response.py new file mode 100644 index 000000000..ffbbfaa59 --- /dev/null +++ b/langfuse/api/metrics/types/metrics_response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class MetricsResponse(UniversalBaseModel): + data: typing.List[typing.Dict[str, typing.Any]] = pydantic.Field() + """ + The metrics data. Each item in the list contains the metric values and dimensions requested in the query. + Format varies based on the query parameters. + Histograms will return an array with [lower, upper, height] tuples. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/metrics_v2/__init__.py b/langfuse/api/metrics_v2/__init__.py new file mode 100644 index 000000000..0421785de --- /dev/null +++ b/langfuse/api/metrics_v2/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import MetricsV2Response +_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/client.py b/langfuse/api/metrics_v2/client.py similarity index 81% rename from langfuse/api/resources/metrics_v_2/client.py rename to langfuse/api/metrics_v2/client.py index 4628c4d61..d6c05914c 100644 --- a/langfuse/api/resources/metrics_v_2/client.py +++ b/langfuse/api/metrics_v2/client.py @@ -1,23 +1,27 @@ # This file was auto-generated by Fern from our API Definition. import typing -from json.decoder import JSONDecodeError -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.metrics_v_2_response import MetricsV2Response +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawMetricsV2Client, RawMetricsV2Client +from .types.metrics_v2response import MetricsV2Response class MetricsV2Client: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = RawMetricsV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMetricsV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMetricsV2Client + """ + return self._raw_client def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None @@ -187,9 +191,9 @@ def metrics( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -197,46 +201,30 @@ def metrics( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.metrics_v_2.metrics( + client.metrics_v2.metrics( query="query", ) """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/metrics", - method="GET", - params={"query": query}, - request_options=request_options, + _response = self._raw_client.metrics( + query=query, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MetricsV2Response, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data class AsyncMetricsV2Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = AsyncRawMetricsV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMetricsV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMetricsV2Client + """ + return self._raw_client async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None @@ -408,9 +396,9 @@ async def metrics( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -421,41 +409,14 @@ async def metrics( async def main() -> None: - await client.metrics_v_2.metrics( + await client.metrics_v2.metrics( query="query", ) asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/metrics", - method="GET", - params={"query": query}, - request_options=request_options, + _response = await self._raw_client.metrics( + query=query, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MetricsV2Response, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data diff --git a/langfuse/api/metrics_v2/raw_client.py b/langfuse/api/metrics_v2/raw_client.py new file mode 100644 index 000000000..b79d55713 --- /dev/null +++ b/langfuse/api/metrics_v2/raw_client.py @@ -0,0 +1,530 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.metrics_v2response import MetricsV2Response + + +class RawMetricsV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MetricsV2Response]: + """ + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. + + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by (see available dimensions above) + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Value to compare against + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by (dimension or metric alias) + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MetricsV2Response] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsV2Response, + parse_obj_as( + type_=MetricsV2Response, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawMetricsV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MetricsV2Response]: + """ + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. + + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by (see available dimensions above) + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Value to compare against + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by (dimension or metric alias) + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MetricsV2Response] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsV2Response, + parse_obj_as( + type_=MetricsV2Response, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/metrics_v2/types/__init__.py b/langfuse/api/metrics_v2/types/__init__.py new file mode 100644 index 000000000..b9510d24f --- /dev/null +++ b/langfuse/api/metrics_v2/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .metrics_v2response import MetricsV2Response +_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".metrics_v2response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/metrics_v2/types/metrics_v2response.py b/langfuse/api/metrics_v2/types/metrics_v2response.py new file mode 100644 index 000000000..461eaf178 --- /dev/null +++ b/langfuse/api/metrics_v2/types/metrics_v2response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class MetricsV2Response(UniversalBaseModel): + data: typing.List[typing.Dict[str, typing.Any]] = pydantic.Field() + """ + The metrics data. Each item in the list contains the metric values and dimensions requested in the query. + Format varies based on the query parameters. + Histograms will return an array with [lower, upper, height] tuples. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/models/__init__.py b/langfuse/api/models/__init__.py new file mode 100644 index 000000000..7ebdb7762 --- /dev/null +++ b/langfuse/api/models/__init__.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateModelRequest, PaginatedModels +_dynamic_imports: typing.Dict[str, str] = { + "CreateModelRequest": ".types", + "PaginatedModels": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateModelRequest", "PaginatedModels"] diff --git a/langfuse/api/models/client.py b/langfuse/api/models/client.py new file mode 100644 index 000000000..9f817b8f6 --- /dev/null +++ b/langfuse/api/models/client.py @@ -0,0 +1,523 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..commons.types.model import Model +from ..commons.types.model_usage_unit import ModelUsageUnit +from ..commons.types.pricing_tier_input import PricingTierInput +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawModelsClient, RawModelsClient +from .types.paginated_models import PaginatedModels + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ModelsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawModelsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawModelsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawModelsClient + """ + return self._raw_client + + def create( + self, + *, + model_name: str, + match_pattern: str, + start_date: typing.Optional[dt.datetime] = OMIT, + unit: typing.Optional[ModelUsageUnit] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[str] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Model: + """ + Create a model + + Parameters + ---------- + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime PaginatedModels: + """ + Get all models + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedModels + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.models.list() + """ + _response = self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> Model: + """ + Get a model + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Model + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.models.get( + id="id", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> None: + """ + Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.models.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + +class AsyncModelsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawModelsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawModelsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawModelsClient + """ + return self._raw_client + + async def create( + self, + *, + model_name: str, + match_pattern: str, + start_date: typing.Optional[dt.datetime] = OMIT, + unit: typing.Optional[ModelUsageUnit] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[str] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Model: + """ + Create a model + + Parameters + ---------- + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime None: + await client.models.create( + model_name="modelName", + match_pattern="matchPattern", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + model_name=model_name, + match_pattern=match_pattern, + start_date=start_date, + unit=unit, + input_price=input_price, + output_price=output_price, + total_price=total_price, + pricing_tiers=pricing_tiers, + tokenizer_id=tokenizer_id, + tokenizer_config=tokenizer_config, + request_options=request_options, + ) + return _response.data + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedModels: + """ + Get all models + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedModels + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.models.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> Model: + """ + Get a model + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Model + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.models.get( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> None: + """ + Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.models.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/langfuse/api/models/raw_client.py b/langfuse/api/models/raw_client.py new file mode 100644 index 000000000..0fdc72319 --- /dev/null +++ b/langfuse/api/models/raw_client.py @@ -0,0 +1,993 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.model import Model +from ..commons.types.model_usage_unit import ModelUsageUnit +from ..commons.types.pricing_tier_input import PricingTierInput +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.paginated_models import PaginatedModels + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawModelsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + model_name: str, + match_pattern: str, + start_date: typing.Optional[dt.datetime] = OMIT, + unit: typing.Optional[ModelUsageUnit] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[str] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Model]: + """ + Create a model + + Parameters + ---------- + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime HttpResponse[PaginatedModels]: + """ + Get all models + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedModels] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/models", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedModels, + parse_obj_as( + type_=PaginatedModels, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[Model]: + """ + Get a model + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Model] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/models/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Model, + parse_obj_as( + type_=Model, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[None]: + """ + Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/models/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawModelsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + model_name: str, + match_pattern: str, + start_date: typing.Optional[dt.datetime] = OMIT, + unit: typing.Optional[ModelUsageUnit] = OMIT, + input_price: typing.Optional[float] = OMIT, + output_price: typing.Optional[float] = OMIT, + total_price: typing.Optional[float] = OMIT, + pricing_tiers: typing.Optional[typing.Sequence[PricingTierInput]] = OMIT, + tokenizer_id: typing.Optional[str] = OMIT, + tokenizer_config: typing.Optional[typing.Any] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Model]: + """ + Create a model + + Parameters + ---------- + model_name : str + Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime AsyncHttpResponse[PaginatedModels]: + """ + Get all models + + Parameters + ---------- + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedModels] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/models", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedModels, + parse_obj_as( + type_=PaginatedModels, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[Model]: + """ + Get a model + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Model] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/models/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Model, + parse_obj_as( + type_=Model, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[None]: + """ + Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/models/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/models/types/__init__.py b/langfuse/api/models/types/__init__.py new file mode 100644 index 000000000..8b4b651c5 --- /dev/null +++ b/langfuse/api/models/types/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_model_request import CreateModelRequest + from .paginated_models import PaginatedModels +_dynamic_imports: typing.Dict[str, str] = { + "CreateModelRequest": ".create_model_request", + "PaginatedModels": ".paginated_models", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateModelRequest", "PaginatedModels"] diff --git a/langfuse/api/resources/models/types/create_model_request.py b/langfuse/api/models/types/create_model_request.py similarity index 54% rename from langfuse/api/resources/models/types/create_model_request.py rename to langfuse/api/models/types/create_model_request.py index 3f9f80119..dc19db944 100644 --- a/langfuse/api/resources/models/types/create_model_request.py +++ b/langfuse/api/models/types/create_model_request.py @@ -3,59 +3,66 @@ import datetime as dt import typing -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 +import pydantic +import typing_extensions from ...commons.types.model_usage_unit import ModelUsageUnit from ...commons.types.pricing_tier_input import PricingTierInput +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata -class CreateModelRequest(pydantic_v1.BaseModel): - model_name: str = pydantic_v1.Field(alias="modelName") +class CreateModelRequest(UniversalBaseModel): + model_name: typing_extensions.Annotated[str, FieldMetadata(alias="modelName")] = ( + pydantic.Field() + ) """ Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/models/types/paginated_models.py b/langfuse/api/models/types/paginated_models.py new file mode 100644 index 000000000..b4bd803cf --- /dev/null +++ b/langfuse/api/models/types/paginated_models.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.model import Model +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class PaginatedModels(UniversalBaseModel): + data: typing.List[Model] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/observations/__init__.py b/langfuse/api/observations/__init__.py new file mode 100644 index 000000000..22b445984 --- /dev/null +++ b/langfuse/api/observations/__init__.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import Observations, ObservationsViews +_dynamic_imports: typing.Dict[str, str] = { + "Observations": ".types", + "ObservationsViews": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/resources/observations/client.py b/langfuse/api/observations/client.py similarity index 67% rename from langfuse/api/resources/observations/client.py rename to langfuse/api/observations/client.py index 77cdd4ee6..6be2a71f4 100644 --- a/langfuse/api/resources/observations/client.py +++ b/langfuse/api/observations/client.py @@ -2,27 +2,29 @@ import datetime as dt import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError + from ..commons.types.observation_level import ObservationLevel from ..commons.types.observations_view import ObservationsView +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawObservationsClient, RawObservationsClient from .types.observations_views import ObservationsViews class ObservationsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = RawObservationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawObservationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawObservationsClient + """ + return self._raw_client def get( self, @@ -47,9 +49,9 @@ def get( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -61,36 +63,10 @@ def get( observation_id="observationId", ) """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/observations/{jsonable_encoder(observation_id)}", - method="GET", - request_options=request_options, + _response = self._raw_client.get( + observation_id, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ObservationsView, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data def get_many( self, @@ -252,9 +228,9 @@ def get_many( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -264,60 +240,39 @@ def get_many( ) client.observations.get_many() """ - _response = self._client_wrapper.httpx_client.request( - "api/public/observations", - method="GET", - params={ - "page": page, - "limit": limit, - "name": name, - "userId": user_id, - "type": type, - "traceId": trace_id, - "level": level, - "parentObservationId": parent_observation_id, - "environment": environment, - "fromStartTime": serialize_datetime(from_start_time) - if from_start_time is not None - else None, - "toStartTime": serialize_datetime(to_start_time) - if to_start_time is not None - else None, - "version": version, - "filter": filter, - }, + _response = self._raw_client.get_many( + page=page, + limit=limit, + name=name, + user_id=user_id, + type=type, + trace_id=trace_id, + level=level, + parent_observation_id=parent_observation_id, + environment=environment, + from_start_time=from_start_time, + to_start_time=to_start_time, + version=version, + filter=filter, request_options=request_options, ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ObservationsViews, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data class AsyncObservationsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = AsyncRawObservationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawObservationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawObservationsClient + """ + return self._raw_client async def get( self, @@ -344,9 +299,9 @@ async def get( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -364,36 +319,10 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/observations/{jsonable_encoder(observation_id)}", - method="GET", - request_options=request_options, + _response = await self._raw_client.get( + observation_id, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ObservationsView, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data async def get_many( self, @@ -557,9 +486,9 @@ async def get_many( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -575,52 +504,20 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/observations", - method="GET", - params={ - "page": page, - "limit": limit, - "name": name, - "userId": user_id, - "type": type, - "traceId": trace_id, - "level": level, - "parentObservationId": parent_observation_id, - "environment": environment, - "fromStartTime": serialize_datetime(from_start_time) - if from_start_time is not None - else None, - "toStartTime": serialize_datetime(to_start_time) - if to_start_time is not None - else None, - "version": version, - "filter": filter, - }, + _response = await self._raw_client.get_many( + page=page, + limit=limit, + name=name, + user_id=user_id, + type=type, + trace_id=trace_id, + level=level, + parent_observation_id=parent_observation_id, + environment=environment, + from_start_time=from_start_time, + to_start_time=to_start_time, + version=version, + filter=filter, request_options=request_options, ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ObservationsViews, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py new file mode 100644 index 000000000..508f8b082 --- /dev/null +++ b/langfuse/api/observations/raw_client.py @@ -0,0 +1,759 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.observation_level import ObservationLevel +from ..commons.types.observations_view import ObservationsView +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.observations_views import ObservationsViews + + +class RawObservationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ObservationsView]: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ObservationsView] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/observations/{jsonable_encoder(observation_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsView, + parse_obj_as( + type_=ObservationsView, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_many( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + name: typing.Optional[str] = None, + user_id: typing.Optional[str] = None, + type: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ObservationsViews]: + """ + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + name : typing.Optional[str] + + user_id : typing.Optional[str] + + type : typing.Optional[str] + + trace_id : typing.Optional[str] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ObservationsViews] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/observations", + method="GET", + params={ + "page": page, + "limit": limit, + "name": name, + "userId": user_id, + "type": type, + "traceId": trace_id, + "level": level, + "parentObservationId": parent_observation_id, + "environment": environment, + "fromStartTime": serialize_datetime(from_start_time) + if from_start_time is not None + else None, + "toStartTime": serialize_datetime(to_start_time) + if to_start_time is not None + else None, + "version": version, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsViews, + parse_obj_as( + type_=ObservationsViews, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawObservationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ObservationsView]: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ObservationsView] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/observations/{jsonable_encoder(observation_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsView, + parse_obj_as( + type_=ObservationsView, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_many( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + name: typing.Optional[str] = None, + user_id: typing.Optional[str] = None, + type: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ObservationsViews]: + """ + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + name : typing.Optional[str] + + user_id : typing.Optional[str] + + type : typing.Optional[str] + + trace_id : typing.Optional[str] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ObservationsViews] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/observations", + method="GET", + params={ + "page": page, + "limit": limit, + "name": name, + "userId": user_id, + "type": type, + "traceId": trace_id, + "level": level, + "parentObservationId": parent_observation_id, + "environment": environment, + "fromStartTime": serialize_datetime(from_start_time) + if from_start_time is not None + else None, + "toStartTime": serialize_datetime(to_start_time) + if to_start_time is not None + else None, + "version": version, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsViews, + parse_obj_as( + type_=ObservationsViews, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/observations/types/__init__.py b/langfuse/api/observations/types/__init__.py new file mode 100644 index 000000000..247b674a1 --- /dev/null +++ b/langfuse/api/observations/types/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .observations import Observations + from .observations_views import ObservationsViews +_dynamic_imports: typing.Dict[str, str] = { + "Observations": ".observations", + "ObservationsViews": ".observations_views", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/observations/types/observations.py b/langfuse/api/observations/types/observations.py new file mode 100644 index 000000000..61a47cdc5 --- /dev/null +++ b/langfuse/api/observations/types/observations.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.observation import Observation +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class Observations(UniversalBaseModel): + data: typing.List[Observation] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/observations/types/observations_views.py b/langfuse/api/observations/types/observations_views.py new file mode 100644 index 000000000..ee682ed39 --- /dev/null +++ b/langfuse/api/observations/types/observations_views.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.observations_view import ObservationsView +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class ObservationsViews(UniversalBaseModel): + data: typing.List[ObservationsView] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/observations_v2/__init__.py b/langfuse/api/observations_v2/__init__.py new file mode 100644 index 000000000..66816e540 --- /dev/null +++ b/langfuse/api/observations_v2/__init__.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ObservationsV2Meta, ObservationsV2Response +_dynamic_imports: typing.Dict[str, str] = { + "ObservationsV2Meta": ".types", + "ObservationsV2Response": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/observations_v2/client.py b/langfuse/api/observations_v2/client.py new file mode 100644 index 000000000..570966958 --- /dev/null +++ b/langfuse/api/observations_v2/client.py @@ -0,0 +1,520 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..commons.types.observation_level import ObservationLevel +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawObservationsV2Client, RawObservationsV2Client +from .types.observations_v2response import ObservationsV2Response + + +class ObservationsV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawObservationsV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawObservationsV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawObservationsV2Client + """ + return self._raw_client + + def get_many( + self, + *, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, + name: typing.Optional[str] = None, + user_id: typing.Optional[str] = None, + type: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsV2Response: + """ + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. + + Parameters + ---------- + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" + + limit : typing.Optional[int] + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. + Defaults to `false` if not provided. + + name : typing.Optional[str] + + user_id : typing.Optional[str] + + type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") + + trace_id : typing.Optional[str] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name (alias: `providedModelName`) + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsV2Response + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.observations_v2.get_many() + """ + _response = self._raw_client.get_many( + fields=fields, + expand_metadata=expand_metadata, + limit=limit, + cursor=cursor, + parse_io_as_json=parse_io_as_json, + name=name, + user_id=user_id, + type=type, + trace_id=trace_id, + level=level, + parent_observation_id=parent_observation_id, + environment=environment, + from_start_time=from_start_time, + to_start_time=to_start_time, + version=version, + filter=filter, + request_options=request_options, + ) + return _response.data + + +class AsyncObservationsV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawObservationsV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawObservationsV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawObservationsV2Client + """ + return self._raw_client + + async def get_many( + self, + *, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, + name: typing.Optional[str] = None, + user_id: typing.Optional[str] = None, + type: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + level: typing.Optional[ObservationLevel] = None, + parent_observation_id: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + from_start_time: typing.Optional[dt.datetime] = None, + to_start_time: typing.Optional[dt.datetime] = None, + version: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsV2Response: + """ + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. + + Parameters + ---------- + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" + + limit : typing.Optional[int] + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. + Defaults to `false` if not provided. + + name : typing.Optional[str] + + user_id : typing.Optional[str] + + type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") + + trace_id : typing.Optional[str] + + level : typing.Optional[ObservationLevel] + Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). + + parent_observation_id : typing.Optional[str] + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for observations where the environment is one of the provided values. + + from_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time on or after this datetime (ISO 8601). + + to_start_time : typing.Optional[dt.datetime] + Retrieve only observations with a start_time before this datetime (ISO 8601). + + version : typing.Optional[str] + Optional filter to only include observations with a certain version. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Observation Fields + - `id` (string) - Observation ID + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + - `name` (string) - Observation name + - `traceId` (string) - Associated trace ID + - `startTime` (datetime) - Observation start time + - `endTime` (datetime) - Observation end time + - `environment` (string) - Environment tag + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + - `statusMessage` (string) - Status message + - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags + + ### Performance Metrics + - `latency` (number) - Latency in seconds (calculated: end_time - start_time) + - `timeToFirstToken` (number) - Time to first token in seconds + - `tokensPerSecond` (number) - Output tokens per second + + ### Token Usage + - `inputTokens` (number) - Number of input tokens + - `outputTokens` (number) - Number of output tokens + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + ### Cost Metrics + - `inputCost` (number) - Input cost in USD + - `outputCost` (number) - Output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Model Information + - `model` (string) - Provided model name (alias: `providedModelName`) + - `promptName` (string) - Associated prompt name + - `promptVersion` (number) - Associated prompt version + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ## Filter Examples + ```json + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsV2Response + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.observations_v2.get_many() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_many( + fields=fields, + expand_metadata=expand_metadata, + limit=limit, + cursor=cursor, + parse_io_as_json=parse_io_as_json, + name=name, + user_id=user_id, + type=type, + trace_id=trace_id, + level=level, + parent_observation_id=parent_observation_id, + environment=environment, + from_start_time=from_start_time, + to_start_time=to_start_time, + version=version, + filter=filter, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/resources/observations_v_2/client.py b/langfuse/api/observations_v2/raw_client.py similarity index 80% rename from langfuse/api/resources/observations_v_2/client.py rename to langfuse/api/observations_v2/raw_client.py index ea9599c69..66beee037 100644 --- a/langfuse/api/resources/observations_v_2/client.py +++ b/langfuse/api/observations_v2/raw_client.py @@ -4,21 +4,22 @@ import typing from json.decoder import JSONDecodeError -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions from ..commons.errors.access_denied_error import AccessDeniedError from ..commons.errors.error import Error from ..commons.errors.method_not_allowed_error import MethodNotAllowedError from ..commons.errors.not_found_error import NotFoundError from ..commons.errors.unauthorized_error import UnauthorizedError from ..commons.types.observation_level import ObservationLevel -from .types.observations_v_2_response import ObservationsV2Response +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.observations_v2response import ObservationsV2Response -class ObservationsV2Client: +class RawObservationsV2Client: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper @@ -42,7 +43,7 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsV2Response: + ) -> HttpResponse[ObservationsV2Response]: """ Get a list of observations with cursor-based pagination and flexible field selection. @@ -220,21 +221,7 @@ def get_many( Returns ------- - ObservationsV2Response - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.observations_v_2.get_many() + HttpResponse[ObservationsV2Response] """ _response = self._client_wrapper.httpx_client.request( "api/public/v2/observations", @@ -265,34 +252,84 @@ def get_many( ) try: if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - ObservationsV2Response, _response.json() - ) # type: ignore + _data = typing.cast( + ObservationsV2Response, + parse_obj_as( + type_=ObservationsV2Response, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 401: raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 403: raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 405: raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 404: raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) -class AsyncObservationsV2Client: +class AsyncRawObservationsV2Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper @@ -316,7 +353,7 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsV2Response: + ) -> AsyncHttpResponse[ObservationsV2Response]: """ Get a list of observations with cursor-based pagination and flexible field selection. @@ -494,29 +531,7 @@ async def get_many( Returns ------- - ObservationsV2Response - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.observations_v_2.get_many() - - - asyncio.run(main()) + AsyncHttpResponse[ObservationsV2Response] """ _response = await self._client_wrapper.httpx_client.request( "api/public/v2/observations", @@ -547,28 +562,78 @@ async def main() -> None: ) try: if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - ObservationsV2Response, _response.json() - ) # type: ignore + _data = typing.cast( + ObservationsV2Response, + parse_obj_as( + type_=ObservationsV2Response, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 401: raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 403: raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 405: raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 404: raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/observations_v2/types/__init__.py b/langfuse/api/observations_v2/types/__init__.py new file mode 100644 index 000000000..6e132aba6 --- /dev/null +++ b/langfuse/api/observations_v2/types/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .observations_v2meta import ObservationsV2Meta + from .observations_v2response import ObservationsV2Response +_dynamic_imports: typing.Dict[str, str] = { + "ObservationsV2Meta": ".observations_v2meta", + "ObservationsV2Response": ".observations_v2response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/observations_v2/types/observations_v2meta.py b/langfuse/api/observations_v2/types/observations_v2meta.py new file mode 100644 index 000000000..8f86a6512 --- /dev/null +++ b/langfuse/api/observations_v2/types/observations_v2meta.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ObservationsV2Meta(UniversalBaseModel): + """ + Metadata for cursor-based pagination + """ + + cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Base64-encoded cursor to use for retrieving the next page. If not present, there are no more results. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/observations_v2/types/observations_v2response.py b/langfuse/api/observations_v2/types/observations_v2response.py new file mode 100644 index 000000000..e833e1918 --- /dev/null +++ b/langfuse/api/observations_v2/types/observations_v2response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .observations_v2meta import ObservationsV2Meta + + +class ObservationsV2Response(UniversalBaseModel): + """ + Response containing observations with field-group-based filtering and cursor-based pagination. + + The `data` array contains observation objects with only the requested field groups included. + Use the `cursor` in `meta` to retrieve the next page of results. + """ + + data: typing.List[typing.Dict[str, typing.Any]] = pydantic.Field() + """ + Array of observation objects. Fields included depend on the `fields` parameter in the request. + """ + + meta: ObservationsV2Meta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/__init__.py b/langfuse/api/opentelemetry/__init__.py new file mode 100644 index 000000000..30caa3796 --- /dev/null +++ b/langfuse/api/opentelemetry/__init__.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + OtelAttribute, + OtelAttributeValue, + OtelResource, + OtelResourceSpan, + OtelScope, + OtelScopeSpan, + OtelSpan, + OtelTraceResponse, + ) +_dynamic_imports: typing.Dict[str, str] = { + "OtelAttribute": ".types", + "OtelAttributeValue": ".types", + "OtelResource": ".types", + "OtelResourceSpan": ".types", + "OtelScope": ".types", + "OtelScopeSpan": ".types", + "OtelSpan": ".types", + "OtelTraceResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "OtelAttribute", + "OtelAttributeValue", + "OtelResource", + "OtelResourceSpan", + "OtelScope", + "OtelScopeSpan", + "OtelSpan", + "OtelTraceResponse", +] diff --git a/langfuse/api/resources/opentelemetry/client.py b/langfuse/api/opentelemetry/client.py similarity index 69% rename from langfuse/api/resources/opentelemetry/client.py rename to langfuse/api/opentelemetry/client.py index de17949d4..13177e5e6 100644 --- a/langfuse/api/resources/opentelemetry/client.py +++ b/langfuse/api/opentelemetry/client.py @@ -1,17 +1,10 @@ # This file was auto-generated by Fern from our API Definition. import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawOpentelemetryClient, RawOpentelemetryClient from .types.otel_resource_span import OtelResourceSpan from .types.otel_trace_response import OtelTraceResponse @@ -21,7 +14,18 @@ class OpentelemetryClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = RawOpentelemetryClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawOpentelemetryClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawOpentelemetryClient + """ + return self._raw_client def export_traces( self, @@ -61,7 +65,8 @@ def export_traces( Examples -------- - from langfuse import ( + from langfuse import LangfuseAPI + from langfuse.opentelemetry import ( OtelAttribute, OtelAttributeValue, OtelResource, @@ -70,9 +75,8 @@ def export_traces( OtelScopeSpan, OtelSpan, ) - from langfuse.client import FernLangfuse - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -130,43 +134,26 @@ def export_traces( ], ) """ - _response = self._client_wrapper.httpx_client.request( - "api/public/otel/v1/traces", - method="POST", - json={"resourceSpans": resource_spans}, - request_options=request_options, - omit=OMIT, + _response = self._raw_client.export_traces( + resource_spans=resource_spans, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(OtelTraceResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data class AsyncOpentelemetryClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = AsyncRawOpentelemetryClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawOpentelemetryClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawOpentelemetryClient + """ + return self._raw_client async def export_traces( self, @@ -208,7 +195,8 @@ async def export_traces( -------- import asyncio - from langfuse import ( + from langfuse import AsyncLangfuseAPI + from langfuse.opentelemetry import ( OtelAttribute, OtelAttributeValue, OtelResource, @@ -217,9 +205,8 @@ async def export_traces( OtelScopeSpan, OtelSpan, ) - from langfuse.client import AsyncFernLangfuse - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -283,35 +270,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/otel/v1/traces", - method="POST", - json={"resourceSpans": resource_spans}, - request_options=request_options, - omit=OMIT, + _response = await self._raw_client.export_traces( + resource_spans=resource_spans, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(OtelTraceResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data diff --git a/langfuse/api/opentelemetry/raw_client.py b/langfuse/api/opentelemetry/raw_client.py new file mode 100644 index 000000000..6b68f909b --- /dev/null +++ b/langfuse/api/opentelemetry/raw_client.py @@ -0,0 +1,291 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.otel_resource_span import OtelResourceSpan +from .types.otel_trace_response import OtelTraceResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawOpentelemetryClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def export_traces( + self, + *, + resource_spans: typing.Sequence[OtelResourceSpan], + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[OtelTraceResponse]: + """ + **OpenTelemetry Traces Ingestion Endpoint** + + This endpoint implements the OTLP/HTTP specification for trace ingestion, providing native OpenTelemetry integration for Langfuse Observability. + + **Supported Formats:** + - Binary Protobuf: `Content-Type: application/x-protobuf` + - JSON Protobuf: `Content-Type: application/json` + - Supports gzip compression via `Content-Encoding: gzip` header + + **Specification Compliance:** + - Conforms to [OTLP/HTTP Trace Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) + - Implements `ExportTraceServiceRequest` message format + + **Documentation:** + - Integration guide: https://langfuse.com/integrations/native/opentelemetry + - Data model: https://langfuse.com/docs/observability/data-model + + Parameters + ---------- + resource_spans : typing.Sequence[OtelResourceSpan] + Array of resource spans containing trace data as defined in the OTLP specification + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[OtelTraceResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/otel/v1/traces", + method="POST", + json={ + "resourceSpans": convert_and_respect_annotation_metadata( + object_=resource_spans, + annotation=typing.Sequence[OtelResourceSpan], + direction="write", + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OtelTraceResponse, + parse_obj_as( + type_=OtelTraceResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawOpentelemetryClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def export_traces( + self, + *, + resource_spans: typing.Sequence[OtelResourceSpan], + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[OtelTraceResponse]: + """ + **OpenTelemetry Traces Ingestion Endpoint** + + This endpoint implements the OTLP/HTTP specification for trace ingestion, providing native OpenTelemetry integration for Langfuse Observability. + + **Supported Formats:** + - Binary Protobuf: `Content-Type: application/x-protobuf` + - JSON Protobuf: `Content-Type: application/json` + - Supports gzip compression via `Content-Encoding: gzip` header + + **Specification Compliance:** + - Conforms to [OTLP/HTTP Trace Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) + - Implements `ExportTraceServiceRequest` message format + + **Documentation:** + - Integration guide: https://langfuse.com/integrations/native/opentelemetry + - Data model: https://langfuse.com/docs/observability/data-model + + Parameters + ---------- + resource_spans : typing.Sequence[OtelResourceSpan] + Array of resource spans containing trace data as defined in the OTLP specification + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[OtelTraceResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/otel/v1/traces", + method="POST", + json={ + "resourceSpans": convert_and_respect_annotation_metadata( + object_=resource_spans, + annotation=typing.Sequence[OtelResourceSpan], + direction="write", + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OtelTraceResponse, + parse_obj_as( + type_=OtelTraceResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/opentelemetry/types/__init__.py b/langfuse/api/opentelemetry/types/__init__.py new file mode 100644 index 000000000..ad2fd4899 --- /dev/null +++ b/langfuse/api/opentelemetry/types/__init__.py @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .otel_attribute import OtelAttribute + from .otel_attribute_value import OtelAttributeValue + from .otel_resource import OtelResource + from .otel_resource_span import OtelResourceSpan + from .otel_scope import OtelScope + from .otel_scope_span import OtelScopeSpan + from .otel_span import OtelSpan + from .otel_trace_response import OtelTraceResponse +_dynamic_imports: typing.Dict[str, str] = { + "OtelAttribute": ".otel_attribute", + "OtelAttributeValue": ".otel_attribute_value", + "OtelResource": ".otel_resource", + "OtelResourceSpan": ".otel_resource_span", + "OtelScope": ".otel_scope", + "OtelScopeSpan": ".otel_scope_span", + "OtelSpan": ".otel_span", + "OtelTraceResponse": ".otel_trace_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "OtelAttribute", + "OtelAttributeValue", + "OtelResource", + "OtelResourceSpan", + "OtelScope", + "OtelScopeSpan", + "OtelSpan", + "OtelTraceResponse", +] diff --git a/langfuse/api/opentelemetry/types/otel_attribute.py b/langfuse/api/opentelemetry/types/otel_attribute.py new file mode 100644 index 000000000..479a46551 --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_attribute.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .otel_attribute_value import OtelAttributeValue + + +class OtelAttribute(UniversalBaseModel): + """ + Key-value attribute pair for resources, scopes, or spans + """ + + key: typing.Optional[str] = pydantic.Field(default=None) + """ + Attribute key (e.g., "service.name", "langfuse.observation.type") + """ + + value: typing.Optional[OtelAttributeValue] = pydantic.Field(default=None) + """ + Attribute value + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_attribute_value.py b/langfuse/api/opentelemetry/types/otel_attribute_value.py new file mode 100644 index 000000000..c381dd28f --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_attribute_value.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class OtelAttributeValue(UniversalBaseModel): + """ + Attribute value wrapper supporting different value types + """ + + string_value: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="stringValue") + ] = pydantic.Field(default=None) + """ + String value + """ + + int_value: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="intValue") + ] = pydantic.Field(default=None) + """ + Integer value + """ + + double_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="doubleValue") + ] = pydantic.Field(default=None) + """ + Double value + """ + + bool_value: typing_extensions.Annotated[ + typing.Optional[bool], FieldMetadata(alias="boolValue") + ] = pydantic.Field(default=None) + """ + Boolean value + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_resource.py b/langfuse/api/opentelemetry/types/otel_resource.py new file mode 100644 index 000000000..c8f1bf2b0 --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_resource.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .otel_attribute import OtelAttribute + + +class OtelResource(UniversalBaseModel): + """ + Resource attributes identifying the source of telemetry + """ + + attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic.Field( + default=None + ) + """ + Resource attributes like service.name, service.version, etc. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_resource_span.py b/langfuse/api/opentelemetry/types/otel_resource_span.py new file mode 100644 index 000000000..d26404cd3 --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_resource_span.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .otel_resource import OtelResource +from .otel_scope_span import OtelScopeSpan + + +class OtelResourceSpan(UniversalBaseModel): + """ + Represents a collection of spans from a single resource as per OTLP specification + """ + + resource: typing.Optional[OtelResource] = pydantic.Field(default=None) + """ + Resource information + """ + + scope_spans: typing_extensions.Annotated[ + typing.Optional[typing.List[OtelScopeSpan]], FieldMetadata(alias="scopeSpans") + ] = pydantic.Field(default=None) + """ + Array of scope spans + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_scope.py b/langfuse/api/opentelemetry/types/otel_scope.py new file mode 100644 index 000000000..a705fc2ec --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_scope.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .otel_attribute import OtelAttribute + + +class OtelScope(UniversalBaseModel): + """ + Instrumentation scope information + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Instrumentation scope name + """ + + version: typing.Optional[str] = pydantic.Field(default=None) + """ + Instrumentation scope version + """ + + attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic.Field( + default=None + ) + """ + Additional scope attributes + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_scope_span.py b/langfuse/api/opentelemetry/types/otel_scope_span.py new file mode 100644 index 000000000..9736454b5 --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_scope_span.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .otel_scope import OtelScope +from .otel_span import OtelSpan + + +class OtelScopeSpan(UniversalBaseModel): + """ + Collection of spans from a single instrumentation scope + """ + + scope: typing.Optional[OtelScope] = pydantic.Field(default=None) + """ + Instrumentation scope information + """ + + spans: typing.Optional[typing.List[OtelSpan]] = pydantic.Field(default=None) + """ + Array of spans + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_span.py b/langfuse/api/opentelemetry/types/otel_span.py new file mode 100644 index 000000000..f6ef51a36 --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_span.py @@ -0,0 +1,76 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .otel_attribute import OtelAttribute + + +class OtelSpan(UniversalBaseModel): + """ + Individual span representing a unit of work or operation + """ + + trace_id: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="traceId") + ] = pydantic.Field(default=None) + """ + Trace ID (16 bytes, hex-encoded string in JSON or Buffer in binary) + """ + + span_id: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="spanId") + ] = pydantic.Field(default=None) + """ + Span ID (8 bytes, hex-encoded string in JSON or Buffer in binary) + """ + + parent_span_id: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="parentSpanId") + ] = pydantic.Field(default=None) + """ + Parent span ID if this is a child span + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Span name describing the operation + """ + + kind: typing.Optional[int] = pydantic.Field(default=None) + """ + Span kind (1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER) + """ + + start_time_unix_nano: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="startTimeUnixNano") + ] = pydantic.Field(default=None) + """ + Start time in nanoseconds since Unix epoch + """ + + end_time_unix_nano: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="endTimeUnixNano") + ] = pydantic.Field(default=None) + """ + End time in nanoseconds since Unix epoch + """ + + attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic.Field( + default=None + ) + """ + Span attributes including Langfuse-specific attributes (langfuse.observation.*) + """ + + status: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Span status object + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/opentelemetry/types/otel_trace_response.py b/langfuse/api/opentelemetry/types/otel_trace_response.py new file mode 100644 index 000000000..02386b088 --- /dev/null +++ b/langfuse/api/opentelemetry/types/otel_trace_response.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class OtelTraceResponse(UniversalBaseModel): + """ + Response from trace export request. Empty object indicates success. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/__init__.py b/langfuse/api/organizations/__init__.py new file mode 100644 index 000000000..469d10a42 --- /dev/null +++ b/langfuse/api/organizations/__init__.py @@ -0,0 +1,73 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + DeleteMembershipRequest, + MembershipDeletionResponse, + MembershipRequest, + MembershipResponse, + MembershipRole, + MembershipsResponse, + OrganizationApiKey, + OrganizationApiKeysResponse, + OrganizationProject, + OrganizationProjectsResponse, + ) +_dynamic_imports: typing.Dict[str, str] = { + "DeleteMembershipRequest": ".types", + "MembershipDeletionResponse": ".types", + "MembershipRequest": ".types", + "MembershipResponse": ".types", + "MembershipRole": ".types", + "MembershipsResponse": ".types", + "OrganizationApiKey": ".types", + "OrganizationApiKeysResponse": ".types", + "OrganizationProject": ".types", + "OrganizationProjectsResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "DeleteMembershipRequest", + "MembershipDeletionResponse", + "MembershipRequest", + "MembershipResponse", + "MembershipRole", + "MembershipsResponse", + "OrganizationApiKey", + "OrganizationApiKeysResponse", + "OrganizationProject", + "OrganizationProjectsResponse", +] diff --git a/langfuse/api/organizations/client.py b/langfuse/api/organizations/client.py new file mode 100644 index 000000000..085c14c5f --- /dev/null +++ b/langfuse/api/organizations/client.py @@ -0,0 +1,756 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawOrganizationsClient, RawOrganizationsClient +from .types.membership_deletion_response import MembershipDeletionResponse +from .types.membership_response import MembershipResponse +from .types.membership_role import MembershipRole +from .types.memberships_response import MembershipsResponse +from .types.organization_api_keys_response import OrganizationApiKeysResponse +from .types.organization_projects_response import OrganizationProjectsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class OrganizationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawOrganizationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawOrganizationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawOrganizationsClient + """ + return self._raw_client + + def get_organization_memberships( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> MembershipsResponse: + """ + Get all memberships for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.get_organization_memberships() + """ + _response = self._raw_client.get_organization_memberships( + request_options=request_options + ) + return _response.data + + def update_organization_membership( + self, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipResponse: + """ + Create or update a membership for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipResponse + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.organizations import MembershipRole + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.update_organization_membership( + user_id="userId", + role=MembershipRole.OWNER, + ) + """ + _response = self._raw_client.update_organization_membership( + user_id=user_id, role=role, request_options=request_options + ) + return _response.data + + def delete_organization_membership( + self, *, user_id: str, request_options: typing.Optional[RequestOptions] = None + ) -> MembershipDeletionResponse: + """ + Delete a membership from the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.delete_organization_membership( + user_id="userId", + ) + """ + _response = self._raw_client.delete_organization_membership( + user_id=user_id, request_options=request_options + ) + return _response.data + + def get_project_memberships( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipsResponse: + """ + Get all memberships for a specific project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.get_project_memberships( + project_id="projectId", + ) + """ + _response = self._raw_client.get_project_memberships( + project_id, request_options=request_options + ) + return _response.data + + def update_project_membership( + self, + project_id: str, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipResponse: + """ + Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipResponse + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.organizations import MembershipRole + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.update_project_membership( + project_id="projectId", + user_id="userId", + role=MembershipRole.OWNER, + ) + """ + _response = self._raw_client.update_project_membership( + project_id, user_id=user_id, role=role, request_options=request_options + ) + return _response.data + + def delete_project_membership( + self, + project_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipDeletionResponse: + """ + Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.delete_project_membership( + project_id="projectId", + user_id="userId", + ) + """ + _response = self._raw_client.delete_project_membership( + project_id, user_id=user_id, request_options=request_options + ) + return _response.data + + def get_organization_projects( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationProjectsResponse: + """ + Get all projects for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationProjectsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.get_organization_projects() + """ + _response = self._raw_client.get_organization_projects( + request_options=request_options + ) + return _response.data + + def get_organization_api_keys( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationApiKeysResponse: + """ + Get all API keys for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationApiKeysResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.organizations.get_organization_api_keys() + """ + _response = self._raw_client.get_organization_api_keys( + request_options=request_options + ) + return _response.data + + +class AsyncOrganizationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawOrganizationsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawOrganizationsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawOrganizationsClient + """ + return self._raw_client + + async def get_organization_memberships( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> MembershipsResponse: + """ + Get all memberships for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.get_organization_memberships() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_organization_memberships( + request_options=request_options + ) + return _response.data + + async def update_organization_membership( + self, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipResponse: + """ + Create or update a membership for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.organizations import MembershipRole + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.update_organization_membership( + user_id="userId", + role=MembershipRole.OWNER, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update_organization_membership( + user_id=user_id, role=role, request_options=request_options + ) + return _response.data + + async def delete_organization_membership( + self, *, user_id: str, request_options: typing.Optional[RequestOptions] = None + ) -> MembershipDeletionResponse: + """ + Delete a membership from the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.delete_organization_membership( + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_organization_membership( + user_id=user_id, request_options=request_options + ) + return _response.data + + async def get_project_memberships( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipsResponse: + """ + Get all memberships for a specific project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.get_project_memberships( + project_id="projectId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_project_memberships( + project_id, request_options=request_options + ) + return _response.data + + async def update_project_membership( + self, + project_id: str, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipResponse: + """ + Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.organizations import MembershipRole + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.update_project_membership( + project_id="projectId", + user_id="userId", + role=MembershipRole.OWNER, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update_project_membership( + project_id, user_id=user_id, role=role, request_options=request_options + ) + return _response.data + + async def delete_project_membership( + self, + project_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> MembershipDeletionResponse: + """ + Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MembershipDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.delete_project_membership( + project_id="projectId", + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_project_membership( + project_id, user_id=user_id, request_options=request_options + ) + return _response.data + + async def get_organization_projects( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationProjectsResponse: + """ + Get all projects for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationProjectsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.get_organization_projects() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_organization_projects( + request_options=request_options + ) + return _response.data + + async def get_organization_api_keys( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> OrganizationApiKeysResponse: + """ + Get all API keys for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OrganizationApiKeysResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.organizations.get_organization_api_keys() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_organization_api_keys( + request_options=request_options + ) + return _response.data diff --git a/langfuse/api/organizations/raw_client.py b/langfuse/api/organizations/raw_client.py new file mode 100644 index 000000000..89596e8bf --- /dev/null +++ b/langfuse/api/organizations/raw_client.py @@ -0,0 +1,1707 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.membership_deletion_response import MembershipDeletionResponse +from .types.membership_response import MembershipResponse +from .types.membership_role import MembershipRole +from .types.memberships_response import MembershipsResponse +from .types.organization_api_keys_response import OrganizationApiKeysResponse +from .types.organization_projects_response import OrganizationProjectsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawOrganizationsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_organization_memberships( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MembershipsResponse]: + """ + Get all memberships for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MembershipsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipsResponse, + parse_obj_as( + type_=MembershipsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def update_organization_membership( + self, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MembershipResponse]: + """ + Create or update a membership for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MembershipResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="PUT", + json={ + "userId": user_id, + "role": role, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipResponse, + parse_obj_as( + type_=MembershipResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_organization_membership( + self, *, user_id: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MembershipDeletionResponse]: + """ + Delete a membership from the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MembershipDeletionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="DELETE", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipDeletionResponse, + parse_obj_as( + type_=MembershipDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_project_memberships( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MembershipsResponse]: + """ + Get all memberships for a specific project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MembershipsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipsResponse, + parse_obj_as( + type_=MembershipsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def update_project_membership( + self, + project_id: str, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MembershipResponse]: + """ + Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MembershipResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="PUT", + json={ + "userId": user_id, + "role": role, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipResponse, + parse_obj_as( + type_=MembershipResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_project_membership( + self, + project_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[MembershipDeletionResponse]: + """ + Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MembershipDeletionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="DELETE", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipDeletionResponse, + parse_obj_as( + type_=MembershipDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_organization_projects( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[OrganizationProjectsResponse]: + """ + Get all projects for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[OrganizationProjectsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/projects", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OrganizationProjectsResponse, + parse_obj_as( + type_=OrganizationProjectsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_organization_api_keys( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[OrganizationApiKeysResponse]: + """ + Get all API keys for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[OrganizationApiKeysResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/apiKeys", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OrganizationApiKeysResponse, + parse_obj_as( + type_=OrganizationApiKeysResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawOrganizationsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_organization_memberships( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MembershipsResponse]: + """ + Get all memberships for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MembershipsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipsResponse, + parse_obj_as( + type_=MembershipsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def update_organization_membership( + self, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MembershipResponse]: + """ + Create or update a membership for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MembershipResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="PUT", + json={ + "userId": user_id, + "role": role, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipResponse, + parse_obj_as( + type_=MembershipResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_organization_membership( + self, *, user_id: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MembershipDeletionResponse]: + """ + Delete a membership from the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MembershipDeletionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/memberships", + method="DELETE", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipDeletionResponse, + parse_obj_as( + type_=MembershipDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_project_memberships( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MembershipsResponse]: + """ + Get all memberships for a specific project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MembershipsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipsResponse, + parse_obj_as( + type_=MembershipsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def update_project_membership( + self, + project_id: str, + *, + user_id: str, + role: MembershipRole, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MembershipResponse]: + """ + Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + role : MembershipRole + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MembershipResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="PUT", + json={ + "userId": user_id, + "role": role, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipResponse, + parse_obj_as( + type_=MembershipResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_project_membership( + self, + project_id: str, + *, + user_id: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[MembershipDeletionResponse]: + """ + Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. + + Parameters + ---------- + project_id : str + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MembershipDeletionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/memberships", + method="DELETE", + json={ + "userId": user_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MembershipDeletionResponse, + parse_obj_as( + type_=MembershipDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_organization_projects( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[OrganizationProjectsResponse]: + """ + Get all projects for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[OrganizationProjectsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/projects", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OrganizationProjectsResponse, + parse_obj_as( + type_=OrganizationProjectsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_organization_api_keys( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[OrganizationApiKeysResponse]: + """ + Get all API keys for the organization associated with the API key (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[OrganizationApiKeysResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/organizations/apiKeys", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OrganizationApiKeysResponse, + parse_obj_as( + type_=OrganizationApiKeysResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/organizations/types/__init__.py b/langfuse/api/organizations/types/__init__.py new file mode 100644 index 000000000..2ac6cb3e7 --- /dev/null +++ b/langfuse/api/organizations/types/__init__.py @@ -0,0 +1,71 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .delete_membership_request import DeleteMembershipRequest + from .membership_deletion_response import MembershipDeletionResponse + from .membership_request import MembershipRequest + from .membership_response import MembershipResponse + from .membership_role import MembershipRole + from .memberships_response import MembershipsResponse + from .organization_api_key import OrganizationApiKey + from .organization_api_keys_response import OrganizationApiKeysResponse + from .organization_project import OrganizationProject + from .organization_projects_response import OrganizationProjectsResponse +_dynamic_imports: typing.Dict[str, str] = { + "DeleteMembershipRequest": ".delete_membership_request", + "MembershipDeletionResponse": ".membership_deletion_response", + "MembershipRequest": ".membership_request", + "MembershipResponse": ".membership_response", + "MembershipRole": ".membership_role", + "MembershipsResponse": ".memberships_response", + "OrganizationApiKey": ".organization_api_key", + "OrganizationApiKeysResponse": ".organization_api_keys_response", + "OrganizationProject": ".organization_project", + "OrganizationProjectsResponse": ".organization_projects_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "DeleteMembershipRequest", + "MembershipDeletionResponse", + "MembershipRequest", + "MembershipResponse", + "MembershipRole", + "MembershipsResponse", + "OrganizationApiKey", + "OrganizationApiKeysResponse", + "OrganizationProject", + "OrganizationProjectsResponse", +] diff --git a/langfuse/api/organizations/types/delete_membership_request.py b/langfuse/api/organizations/types/delete_membership_request.py new file mode 100644 index 000000000..a48c85283 --- /dev/null +++ b/langfuse/api/organizations/types/delete_membership_request.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class DeleteMembershipRequest(UniversalBaseModel): + user_id: typing_extensions.Annotated[str, FieldMetadata(alias="userId")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/membership_deletion_response.py b/langfuse/api/organizations/types/membership_deletion_response.py new file mode 100644 index 000000000..b1c0c3940 --- /dev/null +++ b/langfuse/api/organizations/types/membership_deletion_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class MembershipDeletionResponse(UniversalBaseModel): + message: str + user_id: typing_extensions.Annotated[str, FieldMetadata(alias="userId")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/membership_request.py b/langfuse/api/organizations/types/membership_request.py new file mode 100644 index 000000000..c1edbebdd --- /dev/null +++ b/langfuse/api/organizations/types/membership_request.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .membership_role import MembershipRole + + +class MembershipRequest(UniversalBaseModel): + user_id: typing_extensions.Annotated[str, FieldMetadata(alias="userId")] + role: MembershipRole + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/membership_response.py b/langfuse/api/organizations/types/membership_response.py new file mode 100644 index 000000000..24074c370 --- /dev/null +++ b/langfuse/api/organizations/types/membership_response.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .membership_role import MembershipRole + + +class MembershipResponse(UniversalBaseModel): + user_id: typing_extensions.Annotated[str, FieldMetadata(alias="userId")] + role: MembershipRole + email: str + name: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/organizations/types/membership_role.py b/langfuse/api/organizations/types/membership_role.py similarity index 92% rename from langfuse/api/resources/organizations/types/membership_role.py rename to langfuse/api/organizations/types/membership_role.py index 1721cc0ed..fa84eec3a 100644 --- a/langfuse/api/resources/organizations/types/membership_role.py +++ b/langfuse/api/organizations/types/membership_role.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class MembershipRole(str, enum.Enum): +class MembershipRole(enum.StrEnum): OWNER = "OWNER" ADMIN = "ADMIN" MEMBER = "MEMBER" diff --git a/langfuse/api/organizations/types/memberships_response.py b/langfuse/api/organizations/types/memberships_response.py new file mode 100644 index 000000000..f45dc9942 --- /dev/null +++ b/langfuse/api/organizations/types/memberships_response.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .membership_response import MembershipResponse + + +class MembershipsResponse(UniversalBaseModel): + memberships: typing.List[MembershipResponse] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/organization_api_key.py b/langfuse/api/organizations/types/organization_api_key.py new file mode 100644 index 000000000..572015ab3 --- /dev/null +++ b/langfuse/api/organizations/types/organization_api_key.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class OrganizationApiKey(UniversalBaseModel): + id: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + expires_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="expiresAt") + ] = None + last_used_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastUsedAt") + ] = None + note: typing.Optional[str] = None + public_key: typing_extensions.Annotated[str, FieldMetadata(alias="publicKey")] + display_secret_key: typing_extensions.Annotated[ + str, FieldMetadata(alias="displaySecretKey") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/organization_api_keys_response.py b/langfuse/api/organizations/types/organization_api_keys_response.py new file mode 100644 index 000000000..f0ca789da --- /dev/null +++ b/langfuse/api/organizations/types/organization_api_keys_response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .organization_api_key import OrganizationApiKey + + +class OrganizationApiKeysResponse(UniversalBaseModel): + api_keys: typing_extensions.Annotated[ + typing.List[OrganizationApiKey], FieldMetadata(alias="apiKeys") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/organization_project.py b/langfuse/api/organizations/types/organization_project.py new file mode 100644 index 000000000..9df0fe961 --- /dev/null +++ b/langfuse/api/organizations/types/organization_project.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class OrganizationProject(UniversalBaseModel): + id: str + name: str + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/organizations/types/organization_projects_response.py b/langfuse/api/organizations/types/organization_projects_response.py new file mode 100644 index 000000000..e70925ae0 --- /dev/null +++ b/langfuse/api/organizations/types/organization_projects_response.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .organization_project import OrganizationProject + + +class OrganizationProjectsResponse(UniversalBaseModel): + projects: typing.List[OrganizationProject] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/__init__.py b/langfuse/api/projects/__init__.py new file mode 100644 index 000000000..1eb633f7b --- /dev/null +++ b/langfuse/api/projects/__init__.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + ApiKeyDeletionResponse, + ApiKeyList, + ApiKeyResponse, + ApiKeySummary, + Organization, + Project, + ProjectDeletionResponse, + Projects, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ApiKeyDeletionResponse": ".types", + "ApiKeyList": ".types", + "ApiKeyResponse": ".types", + "ApiKeySummary": ".types", + "Organization": ".types", + "Project": ".types", + "ProjectDeletionResponse": ".types", + "Projects": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ApiKeyDeletionResponse", + "ApiKeyList", + "ApiKeyResponse", + "ApiKeySummary", + "Organization", + "Project", + "ProjectDeletionResponse", + "Projects", +] diff --git a/langfuse/api/projects/client.py b/langfuse/api/projects/client.py new file mode 100644 index 000000000..52350f3b6 --- /dev/null +++ b/langfuse/api/projects/client.py @@ -0,0 +1,760 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawProjectsClient, RawProjectsClient +from .types.api_key_deletion_response import ApiKeyDeletionResponse +from .types.api_key_list import ApiKeyList +from .types.api_key_response import ApiKeyResponse +from .types.project import Project +from .types.project_deletion_response import ProjectDeletionResponse +from .types.projects import Projects + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ProjectsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawProjectsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawProjectsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawProjectsClient + """ + return self._raw_client + + def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> Projects: + """ + Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Projects + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.get() + """ + _response = self._raw_client.get(request_options=request_options) + return _response.data + + def create( + self, + *, + name: str, + retention: int, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Project: + """ + Create a new project (requires organization-scoped API key) + + Parameters + ---------- + name : str + + retention : int + Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.create( + name="name", + retention=1, + ) + """ + _response = self._raw_client.create( + name=name, + retention=retention, + metadata=metadata, + request_options=request_options, + ) + return _response.data + + def update( + self, + project_id: str, + *, + name: str, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + retention: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Project: + """ + Update a project by ID (requires organization-scoped API key). + + Parameters + ---------- + project_id : str + + name : str + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + retention : typing.Optional[int] + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.update( + project_id="projectId", + name="name", + ) + """ + _response = self._raw_client.update( + project_id, + name=name, + metadata=metadata, + retention=retention, + request_options=request_options, + ) + return _response.data + + def delete( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ProjectDeletionResponse: + """ + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProjectDeletionResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.delete( + project_id="projectId", + ) + """ + _response = self._raw_client.delete(project_id, request_options=request_options) + return _response.data + + def get_api_keys( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKeyList: + """ + Get all API keys for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKeyList + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.get_api_keys( + project_id="projectId", + ) + """ + _response = self._raw_client.get_api_keys( + project_id, request_options=request_options + ) + return _response.data + + def create_api_key( + self, + project_id: str, + *, + note: typing.Optional[str] = OMIT, + public_key: typing.Optional[str] = OMIT, + secret_key: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKeyResponse: + """ + Create a new API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + note : typing.Optional[str] + Optional note for the API key + + public_key : typing.Optional[str] + Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + + secret_key : typing.Optional[str] + Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKeyResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.create_api_key( + project_id="projectId", + ) + """ + _response = self._raw_client.create_api_key( + project_id, + note=note, + public_key=public_key, + secret_key=secret_key, + request_options=request_options, + ) + return _response.data + + def delete_api_key( + self, + project_id: str, + api_key_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKeyDeletionResponse: + """ + Delete an API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + api_key_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKeyDeletionResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.projects.delete_api_key( + project_id="projectId", + api_key_id="apiKeyId", + ) + """ + _response = self._raw_client.delete_api_key( + project_id, api_key_id, request_options=request_options + ) + return _response.data + + +class AsyncProjectsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawProjectsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawProjectsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawProjectsClient + """ + return self._raw_client + + async def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> Projects: + """ + Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Projects + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_options=request_options) + return _response.data + + async def create( + self, + *, + name: str, + retention: int, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Project: + """ + Create a new project (requires organization-scoped API key) + + Parameters + ---------- + name : str + + retention : int + Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.create( + name="name", + retention=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + retention=retention, + metadata=metadata, + request_options=request_options, + ) + return _response.data + + async def update( + self, + project_id: str, + *, + name: str, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + retention: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Project: + """ + Update a project by ID (requires organization-scoped API key). + + Parameters + ---------- + project_id : str + + name : str + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + retention : typing.Optional[int] + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Project + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.update( + project_id="projectId", + name="name", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + project_id, + name=name, + metadata=metadata, + retention=retention, + request_options=request_options, + ) + return _response.data + + async def delete( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ProjectDeletionResponse: + """ + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProjectDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.delete( + project_id="projectId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete( + project_id, request_options=request_options + ) + return _response.data + + async def get_api_keys( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKeyList: + """ + Get all API keys for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKeyList + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.get_api_keys( + project_id="projectId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_api_keys( + project_id, request_options=request_options + ) + return _response.data + + async def create_api_key( + self, + project_id: str, + *, + note: typing.Optional[str] = OMIT, + public_key: typing.Optional[str] = OMIT, + secret_key: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKeyResponse: + """ + Create a new API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + note : typing.Optional[str] + Optional note for the API key + + public_key : typing.Optional[str] + Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + + secret_key : typing.Optional[str] + Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKeyResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.create_api_key( + project_id="projectId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_api_key( + project_id, + note=note, + public_key=public_key, + secret_key=secret_key, + request_options=request_options, + ) + return _response.data + + async def delete_api_key( + self, + project_id: str, + api_key_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKeyDeletionResponse: + """ + Delete an API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + api_key_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKeyDeletionResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.projects.delete_api_key( + project_id="projectId", + api_key_id="apiKeyId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_api_key( + project_id, api_key_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/projects/raw_client.py b/langfuse/api/projects/raw_client.py new file mode 100644 index 000000000..524f37d3f --- /dev/null +++ b/langfuse/api/projects/raw_client.py @@ -0,0 +1,1577 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.api_key_deletion_response import ApiKeyDeletionResponse +from .types.api_key_list import ApiKeyList +from .types.api_key_response import ApiKeyResponse +from .types.project import Project +from .types.project_deletion_response import ProjectDeletionResponse +from .types.projects import Projects + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawProjectsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[Projects]: + """ + Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Projects] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/projects", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Projects, + parse_obj_as( + type_=Projects, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create( + self, + *, + name: str, + retention: int, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Project]: + """ + Create a new project (requires organization-scoped API key) + + Parameters + ---------- + name : str + + retention : int + Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Project] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/projects", + method="POST", + json={ + "name": name, + "metadata": metadata, + "retention": retention, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def update( + self, + project_id: str, + *, + name: str, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + retention: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Project]: + """ + Update a project by ID (requires organization-scoped API key). + + Parameters + ---------- + project_id : str + + name : str + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + retention : typing.Optional[int] + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Project] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}", + method="PUT", + json={ + "name": name, + "metadata": metadata, + "retention": retention, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ProjectDeletionResponse]: + """ + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ProjectDeletionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProjectDeletionResponse, + parse_obj_as( + type_=ProjectDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_api_keys( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ApiKeyList]: + """ + Get all API keys for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ApiKeyList] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKeyList, + parse_obj_as( + type_=ApiKeyList, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create_api_key( + self, + project_id: str, + *, + note: typing.Optional[str] = OMIT, + public_key: typing.Optional[str] = OMIT, + secret_key: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ApiKeyResponse]: + """ + Create a new API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + note : typing.Optional[str] + Optional note for the API key + + public_key : typing.Optional[str] + Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + + secret_key : typing.Optional[str] + Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ApiKeyResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", + method="POST", + json={ + "note": note, + "publicKey": public_key, + "secretKey": secret_key, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKeyResponse, + parse_obj_as( + type_=ApiKeyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_api_key( + self, + project_id: str, + api_key_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ApiKeyDeletionResponse]: + """ + Delete an API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + api_key_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ApiKeyDeletionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys/{jsonable_encoder(api_key_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKeyDeletionResponse, + parse_obj_as( + type_=ApiKeyDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawProjectsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[Projects]: + """ + Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Projects] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/projects", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Projects, + parse_obj_as( + type_=Projects, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create( + self, + *, + name: str, + retention: int, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Project]: + """ + Create a new project (requires organization-scoped API key) + + Parameters + ---------- + name : str + + retention : int + Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Project] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/projects", + method="POST", + json={ + "name": name, + "metadata": metadata, + "retention": retention, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def update( + self, + project_id: str, + *, + name: str, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + retention: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Project]: + """ + Update a project by ID (requires organization-scoped API key). + + Parameters + ---------- + project_id : str + + name : str + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + Optional metadata for the project + + retention : typing.Optional[int] + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Project] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}", + method="PUT", + json={ + "name": name, + "metadata": metadata, + "retention": retention, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Project, + parse_obj_as( + type_=Project, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ProjectDeletionResponse]: + """ + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ProjectDeletionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProjectDeletionResponse, + parse_obj_as( + type_=ProjectDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_api_keys( + self, + project_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ApiKeyList]: + """ + Get all API keys for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ApiKeyList] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKeyList, + parse_obj_as( + type_=ApiKeyList, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create_api_key( + self, + project_id: str, + *, + note: typing.Optional[str] = OMIT, + public_key: typing.Optional[str] = OMIT, + secret_key: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ApiKeyResponse]: + """ + Create a new API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + note : typing.Optional[str] + Optional note for the API key + + public_key : typing.Optional[str] + Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. + + secret_key : typing.Optional[str] + Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ApiKeyResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", + method="POST", + json={ + "note": note, + "publicKey": public_key, + "secretKey": secret_key, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKeyResponse, + parse_obj_as( + type_=ApiKeyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_api_key( + self, + project_id: str, + api_key_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ApiKeyDeletionResponse]: + """ + Delete an API key for a project (requires organization-scoped API key) + + Parameters + ---------- + project_id : str + + api_key_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ApiKeyDeletionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys/{jsonable_encoder(api_key_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKeyDeletionResponse, + parse_obj_as( + type_=ApiKeyDeletionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/projects/types/__init__.py b/langfuse/api/projects/types/__init__.py new file mode 100644 index 000000000..348f521c0 --- /dev/null +++ b/langfuse/api/projects/types/__init__.py @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .api_key_deletion_response import ApiKeyDeletionResponse + from .api_key_list import ApiKeyList + from .api_key_response import ApiKeyResponse + from .api_key_summary import ApiKeySummary + from .organization import Organization + from .project import Project + from .project_deletion_response import ProjectDeletionResponse + from .projects import Projects +_dynamic_imports: typing.Dict[str, str] = { + "ApiKeyDeletionResponse": ".api_key_deletion_response", + "ApiKeyList": ".api_key_list", + "ApiKeyResponse": ".api_key_response", + "ApiKeySummary": ".api_key_summary", + "Organization": ".organization", + "Project": ".project", + "ProjectDeletionResponse": ".project_deletion_response", + "Projects": ".projects", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ApiKeyDeletionResponse", + "ApiKeyList", + "ApiKeyResponse", + "ApiKeySummary", + "Organization", + "Project", + "ProjectDeletionResponse", + "Projects", +] diff --git a/langfuse/api/projects/types/api_key_deletion_response.py b/langfuse/api/projects/types/api_key_deletion_response.py new file mode 100644 index 000000000..fd6a69448 --- /dev/null +++ b/langfuse/api/projects/types/api_key_deletion_response.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ApiKeyDeletionResponse(UniversalBaseModel): + """ + Response for API key deletion + """ + + success: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/api_key_list.py b/langfuse/api/projects/types/api_key_list.py new file mode 100644 index 000000000..bf38d9be1 --- /dev/null +++ b/langfuse/api/projects/types/api_key_list.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .api_key_summary import ApiKeySummary + + +class ApiKeyList(UniversalBaseModel): + """ + List of API keys for a project + """ + + api_keys: typing_extensions.Annotated[ + typing.List[ApiKeySummary], FieldMetadata(alias="apiKeys") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/api_key_response.py b/langfuse/api/projects/types/api_key_response.py new file mode 100644 index 000000000..06ad54610 --- /dev/null +++ b/langfuse/api/projects/types/api_key_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class ApiKeyResponse(UniversalBaseModel): + """ + Response for API key creation + """ + + id: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + public_key: typing_extensions.Annotated[str, FieldMetadata(alias="publicKey")] + secret_key: typing_extensions.Annotated[str, FieldMetadata(alias="secretKey")] + display_secret_key: typing_extensions.Annotated[ + str, FieldMetadata(alias="displaySecretKey") + ] + note: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/api_key_summary.py b/langfuse/api/projects/types/api_key_summary.py new file mode 100644 index 000000000..68d21421b --- /dev/null +++ b/langfuse/api/projects/types/api_key_summary.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class ApiKeySummary(UniversalBaseModel): + """ + Summary of an API key + """ + + id: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + expires_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="expiresAt") + ] = None + last_used_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastUsedAt") + ] = None + note: typing.Optional[str] = None + public_key: typing_extensions.Annotated[str, FieldMetadata(alias="publicKey")] + display_secret_key: typing_extensions.Annotated[ + str, FieldMetadata(alias="displaySecretKey") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/organization.py b/langfuse/api/projects/types/organization.py new file mode 100644 index 000000000..c6d1108b3 --- /dev/null +++ b/langfuse/api/projects/types/organization.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class Organization(UniversalBaseModel): + id: str = pydantic.Field() + """ + The unique identifier of the organization + """ + + name: str = pydantic.Field() + """ + The name of the organization + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/project.py b/langfuse/api/projects/types/project.py new file mode 100644 index 000000000..b8b83cf7e --- /dev/null +++ b/langfuse/api/projects/types/project.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .organization import Organization + + +class Project(UniversalBaseModel): + id: str + name: str + organization: Organization = pydantic.Field() + """ + The organization this project belongs to + """ + + metadata: typing.Dict[str, typing.Any] = pydantic.Field() + """ + Metadata for the project + """ + + retention_days: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="retentionDays") + ] = pydantic.Field(default=None) + """ + Number of days to retain data. Null or 0 means no retention. Omitted if no retention is configured. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/project_deletion_response.py b/langfuse/api/projects/types/project_deletion_response.py new file mode 100644 index 000000000..e3d471c78 --- /dev/null +++ b/langfuse/api/projects/types/project_deletion_response.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ProjectDeletionResponse(UniversalBaseModel): + success: bool + message: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/projects/types/projects.py b/langfuse/api/projects/types/projects.py new file mode 100644 index 000000000..5771c0051 --- /dev/null +++ b/langfuse/api/projects/types/projects.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .project import Project + + +class Projects(UniversalBaseModel): + data: typing.List[Project] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/prompt_version/__init__.py b/langfuse/api/prompt_version/__init__.py similarity index 76% rename from langfuse/api/resources/prompt_version/__init__.py rename to langfuse/api/prompt_version/__init__.py index f3ea2659b..5cde0202d 100644 --- a/langfuse/api/resources/prompt_version/__init__.py +++ b/langfuse/api/prompt_version/__init__.py @@ -1,2 +1,4 @@ # This file was auto-generated by Fern from our API Definition. +# isort: skip_file + diff --git a/langfuse/api/prompt_version/client.py b/langfuse/api/prompt_version/client.py new file mode 100644 index 000000000..f212447a4 --- /dev/null +++ b/langfuse/api/prompt_version/client.py @@ -0,0 +1,157 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..prompts.types.prompt import Prompt +from .raw_client import AsyncRawPromptVersionClient, RawPromptVersionClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class PromptVersionClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawPromptVersionClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawPromptVersionClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawPromptVersionClient + """ + return self._raw_client + + def update( + self, + name: str, + version: int, + *, + new_labels: typing.Sequence[str], + request_options: typing.Optional[RequestOptions] = None, + ) -> Prompt: + """ + Update labels for a specific prompt version + + Parameters + ---------- + name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : int + Version of the prompt to update + + new_labels : typing.Sequence[str] + New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Prompt + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.prompt_version.update( + name="name", + version=1, + new_labels=["newLabels", "newLabels"], + ) + """ + _response = self._raw_client.update( + name, version, new_labels=new_labels, request_options=request_options + ) + return _response.data + + +class AsyncPromptVersionClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawPromptVersionClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawPromptVersionClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawPromptVersionClient + """ + return self._raw_client + + async def update( + self, + name: str, + version: int, + *, + new_labels: typing.Sequence[str], + request_options: typing.Optional[RequestOptions] = None, + ) -> Prompt: + """ + Update labels for a specific prompt version + + Parameters + ---------- + name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : int + Version of the prompt to update + + new_labels : typing.Sequence[str] + New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Prompt + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.prompt_version.update( + name="name", + version=1, + new_labels=["newLabels", "newLabels"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + name, version, new_labels=new_labels, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/prompt_version/raw_client.py b/langfuse/api/prompt_version/raw_client.py new file mode 100644 index 000000000..7d1e8d3f2 --- /dev/null +++ b/langfuse/api/prompt_version/raw_client.py @@ -0,0 +1,264 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..prompts.types.prompt import Prompt + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawPromptVersionClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def update( + self, + name: str, + version: int, + *, + new_labels: typing.Sequence[str], + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Prompt]: + """ + Update labels for a specific prompt version + + Parameters + ---------- + name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : int + Version of the prompt to update + + new_labels : typing.Sequence[str] + New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Prompt] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(name)}/versions/{jsonable_encoder(version)}", + method="PATCH", + json={ + "newLabels": new_labels, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Prompt, + parse_obj_as( + type_=Prompt, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawPromptVersionClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def update( + self, + name: str, + version: int, + *, + new_labels: typing.Sequence[str], + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Prompt]: + """ + Update labels for a specific prompt version + + Parameters + ---------- + name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : int + Version of the prompt to update + + new_labels : typing.Sequence[str] + New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Prompt] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(name)}/versions/{jsonable_encoder(version)}", + method="PATCH", + json={ + "newLabels": new_labels, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Prompt, + parse_obj_as( + type_=Prompt, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/prompts/__init__.py b/langfuse/api/prompts/__init__.py new file mode 100644 index 000000000..13e115e7b --- /dev/null +++ b/langfuse/api/prompts/__init__.py @@ -0,0 +1,100 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + BasePrompt, + ChatMessage, + ChatMessageType, + ChatMessageWithPlaceholders, + ChatPrompt, + CreateChatPromptRequest, + CreateChatPromptType, + CreatePromptRequest, + CreateTextPromptRequest, + CreateTextPromptType, + PlaceholderMessage, + PlaceholderMessageType, + Prompt, + PromptMeta, + PromptMetaListResponse, + PromptType, + Prompt_Chat, + Prompt_Text, + TextPrompt, + ) +_dynamic_imports: typing.Dict[str, str] = { + "BasePrompt": ".types", + "ChatMessage": ".types", + "ChatMessageType": ".types", + "ChatMessageWithPlaceholders": ".types", + "ChatPrompt": ".types", + "CreateChatPromptRequest": ".types", + "CreateChatPromptType": ".types", + "CreatePromptRequest": ".types", + "CreateTextPromptRequest": ".types", + "CreateTextPromptType": ".types", + "PlaceholderMessage": ".types", + "PlaceholderMessageType": ".types", + "Prompt": ".types", + "PromptMeta": ".types", + "PromptMetaListResponse": ".types", + "PromptType": ".types", + "Prompt_Chat": ".types", + "Prompt_Text": ".types", + "TextPrompt": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BasePrompt", + "ChatMessage", + "ChatMessageType", + "ChatMessageWithPlaceholders", + "ChatPrompt", + "CreateChatPromptRequest", + "CreateChatPromptType", + "CreatePromptRequest", + "CreateTextPromptRequest", + "CreateTextPromptType", + "PlaceholderMessage", + "PlaceholderMessageType", + "Prompt", + "PromptMeta", + "PromptMetaListResponse", + "PromptType", + "Prompt_Chat", + "Prompt_Text", + "TextPrompt", +] diff --git a/langfuse/api/prompts/client.py b/langfuse/api/prompts/client.py new file mode 100644 index 000000000..fc6203787 --- /dev/null +++ b/langfuse/api/prompts/client.py @@ -0,0 +1,534 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawPromptsClient, RawPromptsClient +from .types.create_prompt_request import CreatePromptRequest +from .types.prompt import Prompt +from .types.prompt_meta_list_response import PromptMetaListResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class PromptsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawPromptsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawPromptsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawPromptsClient + """ + return self._raw_client + + def get( + self, + prompt_name: str, + *, + version: typing.Optional[int] = None, + label: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> Prompt: + """ + Get a prompt + + Parameters + ---------- + prompt_name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : typing.Optional[int] + Version of the prompt to be retrieved. + + label : typing.Optional[str] + Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Prompt + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.prompts.get( + prompt_name="promptName", + ) + """ + _response = self._raw_client.get( + prompt_name, version=version, label=label, request_options=request_options + ) + return _response.data + + def list( + self, + *, + name: typing.Optional[str] = None, + label: typing.Optional[str] = None, + tag: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_updated_at: typing.Optional[dt.datetime] = None, + to_updated_at: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PromptMetaListResponse: + """ + Get a list of prompt names with versions and labels + + Parameters + ---------- + name : typing.Optional[str] + + label : typing.Optional[str] + + tag : typing.Optional[str] + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + from_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) + + to_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PromptMetaListResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.prompts.list() + """ + _response = self._raw_client.list( + name=name, + label=label, + tag=tag, + page=page, + limit=limit, + from_updated_at=from_updated_at, + to_updated_at=to_updated_at, + request_options=request_options, + ) + return _response.data + + def create( + self, + *, + request: CreatePromptRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> Prompt: + """ + Create a new version for the prompt with the given `name` + + Parameters + ---------- + request : CreatePromptRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Prompt + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.prompts import ( + ChatMessage, + CreateChatPromptRequest, + CreateChatPromptType, + ) + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.prompts.create( + request=CreateChatPromptRequest( + name="name", + prompt=[ + ChatMessage( + role="role", + content="content", + ), + ChatMessage( + role="role", + content="content", + ), + ], + type=CreateChatPromptType.CHAT, + ), + ) + """ + _response = self._raw_client.create( + request=request, request_options=request_options + ) + return _response.data + + def delete( + self, + prompt_name: str, + *, + label: typing.Optional[str] = None, + version: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: + """ + Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. + + Parameters + ---------- + prompt_name : str + The name of the prompt + + label : typing.Optional[str] + Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + + version : typing.Optional[int] + Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.prompts.delete( + prompt_name="promptName", + ) + """ + _response = self._raw_client.delete( + prompt_name, label=label, version=version, request_options=request_options + ) + return _response.data + + +class AsyncPromptsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawPromptsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawPromptsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawPromptsClient + """ + return self._raw_client + + async def get( + self, + prompt_name: str, + *, + version: typing.Optional[int] = None, + label: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> Prompt: + """ + Get a prompt + + Parameters + ---------- + prompt_name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : typing.Optional[int] + Version of the prompt to be retrieved. + + label : typing.Optional[str] + Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Prompt + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.prompts.get( + prompt_name="promptName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + prompt_name, version=version, label=label, request_options=request_options + ) + return _response.data + + async def list( + self, + *, + name: typing.Optional[str] = None, + label: typing.Optional[str] = None, + tag: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_updated_at: typing.Optional[dt.datetime] = None, + to_updated_at: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PromptMetaListResponse: + """ + Get a list of prompt names with versions and labels + + Parameters + ---------- + name : typing.Optional[str] + + label : typing.Optional[str] + + tag : typing.Optional[str] + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + from_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) + + to_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PromptMetaListResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.prompts.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + name=name, + label=label, + tag=tag, + page=page, + limit=limit, + from_updated_at=from_updated_at, + to_updated_at=to_updated_at, + request_options=request_options, + ) + return _response.data + + async def create( + self, + *, + request: CreatePromptRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> Prompt: + """ + Create a new version for the prompt with the given `name` + + Parameters + ---------- + request : CreatePromptRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Prompt + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.prompts import ( + ChatMessage, + CreateChatPromptRequest, + CreateChatPromptType, + ) + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.prompts.create( + request=CreateChatPromptRequest( + name="name", + prompt=[ + ChatMessage( + role="role", + content="content", + ), + ChatMessage( + role="role", + content="content", + ), + ], + type=CreateChatPromptType.CHAT, + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + request=request, request_options=request_options + ) + return _response.data + + async def delete( + self, + prompt_name: str, + *, + label: typing.Optional[str] = None, + version: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: + """ + Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. + + Parameters + ---------- + prompt_name : str + The name of the prompt + + label : typing.Optional[str] + Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + + version : typing.Optional[int] + Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.prompts.delete( + prompt_name="promptName", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete( + prompt_name, label=label, version=version, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/prompts/raw_client.py b/langfuse/api/prompts/raw_client.py new file mode 100644 index 000000000..81b108968 --- /dev/null +++ b/langfuse/api/prompts/raw_client.py @@ -0,0 +1,977 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.create_prompt_request import CreatePromptRequest +from .types.prompt import Prompt +from .types.prompt_meta_list_response import PromptMetaListResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawPromptsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, + prompt_name: str, + *, + version: typing.Optional[int] = None, + label: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Prompt]: + """ + Get a prompt + + Parameters + ---------- + prompt_name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : typing.Optional[int] + Version of the prompt to be retrieved. + + label : typing.Optional[str] + Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Prompt] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", + method="GET", + params={ + "version": version, + "label": label, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Prompt, + parse_obj_as( + type_=Prompt, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list( + self, + *, + name: typing.Optional[str] = None, + label: typing.Optional[str] = None, + tag: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_updated_at: typing.Optional[dt.datetime] = None, + to_updated_at: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PromptMetaListResponse]: + """ + Get a list of prompt names with versions and labels + + Parameters + ---------- + name : typing.Optional[str] + + label : typing.Optional[str] + + tag : typing.Optional[str] + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + from_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) + + to_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PromptMetaListResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/prompts", + method="GET", + params={ + "name": name, + "label": label, + "tag": tag, + "page": page, + "limit": limit, + "fromUpdatedAt": serialize_datetime(from_updated_at) + if from_updated_at is not None + else None, + "toUpdatedAt": serialize_datetime(to_updated_at) + if to_updated_at is not None + else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PromptMetaListResponse, + parse_obj_as( + type_=PromptMetaListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create( + self, + *, + request: CreatePromptRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Prompt]: + """ + Create a new version for the prompt with the given `name` + + Parameters + ---------- + request : CreatePromptRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Prompt] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v2/prompts", + method="POST", + json=convert_and_respect_annotation_metadata( + object_=request, annotation=CreatePromptRequest, direction="write" + ), + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Prompt, + parse_obj_as( + type_=Prompt, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, + prompt_name: str, + *, + label: typing.Optional[str] = None, + version: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[None]: + """ + Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. + + Parameters + ---------- + prompt_name : str + The name of the prompt + + label : typing.Optional[str] + Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + + version : typing.Optional[int] + Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", + method="DELETE", + params={ + "label": label, + "version": version, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawPromptsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, + prompt_name: str, + *, + version: typing.Optional[int] = None, + label: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Prompt]: + """ + Get a prompt + + Parameters + ---------- + prompt_name : str + The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), + the folder path must be URL encoded. + + version : typing.Optional[int] + Version of the prompt to be retrieved. + + label : typing.Optional[str] + Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Prompt] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", + method="GET", + params={ + "version": version, + "label": label, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Prompt, + parse_obj_as( + type_=Prompt, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list( + self, + *, + name: typing.Optional[str] = None, + label: typing.Optional[str] = None, + tag: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_updated_at: typing.Optional[dt.datetime] = None, + to_updated_at: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PromptMetaListResponse]: + """ + Get a list of prompt names with versions and labels + + Parameters + ---------- + name : typing.Optional[str] + + label : typing.Optional[str] + + tag : typing.Optional[str] + + page : typing.Optional[int] + page number, starts at 1 + + limit : typing.Optional[int] + limit of items per page + + from_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) + + to_updated_at : typing.Optional[dt.datetime] + Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PromptMetaListResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/prompts", + method="GET", + params={ + "name": name, + "label": label, + "tag": tag, + "page": page, + "limit": limit, + "fromUpdatedAt": serialize_datetime(from_updated_at) + if from_updated_at is not None + else None, + "toUpdatedAt": serialize_datetime(to_updated_at) + if to_updated_at is not None + else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PromptMetaListResponse, + parse_obj_as( + type_=PromptMetaListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create( + self, + *, + request: CreatePromptRequest, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Prompt]: + """ + Create a new version for the prompt with the given `name` + + Parameters + ---------- + request : CreatePromptRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Prompt] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v2/prompts", + method="POST", + json=convert_and_respect_annotation_metadata( + object_=request, annotation=CreatePromptRequest, direction="write" + ), + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Prompt, + parse_obj_as( + type_=Prompt, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, + prompt_name: str, + *, + label: typing.Optional[str] = None, + version: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[None]: + """ + Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. + + Parameters + ---------- + prompt_name : str + The name of the prompt + + label : typing.Optional[str] + Optional label to filter deletion. If specified, deletes all prompt versions that have this label. + + version : typing.Optional[int] + Optional version to filter deletion. If specified, deletes only this specific version of the prompt. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", + method="DELETE", + params={ + "label": label, + "version": version, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/prompts/types/__init__.py b/langfuse/api/prompts/types/__init__.py new file mode 100644 index 000000000..91baf935b --- /dev/null +++ b/langfuse/api/prompts/types/__init__.py @@ -0,0 +1,96 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .base_prompt import BasePrompt + from .chat_message import ChatMessage + from .chat_message_type import ChatMessageType + from .chat_message_with_placeholders import ChatMessageWithPlaceholders + from .chat_prompt import ChatPrompt + from .create_chat_prompt_request import CreateChatPromptRequest + from .create_chat_prompt_type import CreateChatPromptType + from .create_prompt_request import CreatePromptRequest + from .create_text_prompt_request import CreateTextPromptRequest + from .create_text_prompt_type import CreateTextPromptType + from .placeholder_message import PlaceholderMessage + from .placeholder_message_type import PlaceholderMessageType + from .prompt import Prompt, Prompt_Chat, Prompt_Text + from .prompt_meta import PromptMeta + from .prompt_meta_list_response import PromptMetaListResponse + from .prompt_type import PromptType + from .text_prompt import TextPrompt +_dynamic_imports: typing.Dict[str, str] = { + "BasePrompt": ".base_prompt", + "ChatMessage": ".chat_message", + "ChatMessageType": ".chat_message_type", + "ChatMessageWithPlaceholders": ".chat_message_with_placeholders", + "ChatPrompt": ".chat_prompt", + "CreateChatPromptRequest": ".create_chat_prompt_request", + "CreateChatPromptType": ".create_chat_prompt_type", + "CreatePromptRequest": ".create_prompt_request", + "CreateTextPromptRequest": ".create_text_prompt_request", + "CreateTextPromptType": ".create_text_prompt_type", + "PlaceholderMessage": ".placeholder_message", + "PlaceholderMessageType": ".placeholder_message_type", + "Prompt": ".prompt", + "PromptMeta": ".prompt_meta", + "PromptMetaListResponse": ".prompt_meta_list_response", + "PromptType": ".prompt_type", + "Prompt_Chat": ".prompt", + "Prompt_Text": ".prompt", + "TextPrompt": ".text_prompt", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BasePrompt", + "ChatMessage", + "ChatMessageType", + "ChatMessageWithPlaceholders", + "ChatPrompt", + "CreateChatPromptRequest", + "CreateChatPromptType", + "CreatePromptRequest", + "CreateTextPromptRequest", + "CreateTextPromptType", + "PlaceholderMessage", + "PlaceholderMessageType", + "Prompt", + "PromptMeta", + "PromptMetaListResponse", + "PromptType", + "Prompt_Chat", + "Prompt_Text", + "TextPrompt", +] diff --git a/langfuse/api/prompts/types/base_prompt.py b/langfuse/api/prompts/types/base_prompt.py new file mode 100644 index 000000000..bd9461600 --- /dev/null +++ b/langfuse/api/prompts/types/base_prompt.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class BasePrompt(UniversalBaseModel): + name: str + version: int + config: typing.Any + labels: typing.List[str] = pydantic.Field() + """ + List of deployment labels of this prompt version. + """ + + tags: typing.List[str] = pydantic.Field() + """ + List of tags. Used to filter via UI and API. The same across versions of a prompt. + """ + + commit_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="commitMessage") + ] = pydantic.Field(default=None) + """ + Commit message for this prompt version. + """ + + resolution_graph: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Any]], + FieldMetadata(alias="resolutionGraph"), + ] = pydantic.Field(default=None) + """ + The dependency resolution graph for the current prompt. Null if prompt has no dependencies. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/chat_message.py b/langfuse/api/prompts/types/chat_message.py new file mode 100644 index 000000000..9d0b4c0f6 --- /dev/null +++ b/langfuse/api/prompts/types/chat_message.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .chat_message_type import ChatMessageType + + +class ChatMessage(UniversalBaseModel): + role: str + content: str + type: typing.Optional[ChatMessageType] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/chat_message_type.py b/langfuse/api/prompts/types/chat_message_type.py new file mode 100644 index 000000000..75eac8a20 --- /dev/null +++ b/langfuse/api/prompts/types/chat_message_type.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class ChatMessageType(enum.StrEnum): + CHATMESSAGE = "chatmessage" + + def visit(self, chatmessage: typing.Callable[[], T_Result]) -> T_Result: + if self is ChatMessageType.CHATMESSAGE: + return chatmessage() diff --git a/langfuse/api/prompts/types/chat_message_with_placeholders.py b/langfuse/api/prompts/types/chat_message_with_placeholders.py new file mode 100644 index 000000000..e077ca144 --- /dev/null +++ b/langfuse/api/prompts/types/chat_message_with_placeholders.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .chat_message import ChatMessage +from .placeholder_message import PlaceholderMessage + +ChatMessageWithPlaceholders = typing.Union[ChatMessage, PlaceholderMessage] diff --git a/langfuse/api/prompts/types/chat_prompt.py b/langfuse/api/prompts/types/chat_prompt.py new file mode 100644 index 000000000..ce347537f --- /dev/null +++ b/langfuse/api/prompts/types/chat_prompt.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_prompt import BasePrompt +from .chat_message_with_placeholders import ChatMessageWithPlaceholders + + +class ChatPrompt(BasePrompt): + prompt: typing.List[ChatMessageWithPlaceholders] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/create_chat_prompt_request.py b/langfuse/api/prompts/types/create_chat_prompt_request.py new file mode 100644 index 000000000..0fe1de0c1 --- /dev/null +++ b/langfuse/api/prompts/types/create_chat_prompt_request.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .chat_message_with_placeholders import ChatMessageWithPlaceholders +from .create_chat_prompt_type import CreateChatPromptType + + +class CreateChatPromptRequest(UniversalBaseModel): + name: str + prompt: typing.List[ChatMessageWithPlaceholders] + config: typing.Optional[typing.Any] = None + type: CreateChatPromptType + labels: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of deployment labels of this prompt version. + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of tags to apply to all versions of this prompt. + """ + + commit_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="commitMessage") + ] = pydantic.Field(default=None) + """ + Commit message for this prompt version. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/create_chat_prompt_type.py b/langfuse/api/prompts/types/create_chat_prompt_type.py new file mode 100644 index 000000000..12f6748a4 --- /dev/null +++ b/langfuse/api/prompts/types/create_chat_prompt_type.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class CreateChatPromptType(enum.StrEnum): + CHAT = "chat" + + def visit(self, chat: typing.Callable[[], T_Result]) -> T_Result: + if self is CreateChatPromptType.CHAT: + return chat() diff --git a/langfuse/api/prompts/types/create_prompt_request.py b/langfuse/api/prompts/types/create_prompt_request.py new file mode 100644 index 000000000..13d75e7e1 --- /dev/null +++ b/langfuse/api/prompts/types/create_prompt_request.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .create_chat_prompt_request import CreateChatPromptRequest +from .create_text_prompt_request import CreateTextPromptRequest + +CreatePromptRequest = typing.Union[CreateChatPromptRequest, CreateTextPromptRequest] diff --git a/langfuse/api/prompts/types/create_text_prompt_request.py b/langfuse/api/prompts/types/create_text_prompt_request.py new file mode 100644 index 000000000..be87a7dde --- /dev/null +++ b/langfuse/api/prompts/types/create_text_prompt_request.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .create_text_prompt_type import CreateTextPromptType + + +class CreateTextPromptRequest(UniversalBaseModel): + name: str + prompt: str + config: typing.Optional[typing.Any] = None + type: typing.Optional[CreateTextPromptType] = None + labels: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of deployment labels of this prompt version. + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of tags to apply to all versions of this prompt. + """ + + commit_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="commitMessage") + ] = pydantic.Field(default=None) + """ + Commit message for this prompt version. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/create_text_prompt_type.py b/langfuse/api/prompts/types/create_text_prompt_type.py new file mode 100644 index 000000000..825fcee0d --- /dev/null +++ b/langfuse/api/prompts/types/create_text_prompt_type.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class CreateTextPromptType(enum.StrEnum): + TEXT = "text" + + def visit(self, text: typing.Callable[[], T_Result]) -> T_Result: + if self is CreateTextPromptType.TEXT: + return text() diff --git a/langfuse/api/prompts/types/placeholder_message.py b/langfuse/api/prompts/types/placeholder_message.py new file mode 100644 index 000000000..9397e20d0 --- /dev/null +++ b/langfuse/api/prompts/types/placeholder_message.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .placeholder_message_type import PlaceholderMessageType + + +class PlaceholderMessage(UniversalBaseModel): + name: str + type: typing.Optional[PlaceholderMessageType] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/placeholder_message_type.py b/langfuse/api/prompts/types/placeholder_message_type.py new file mode 100644 index 000000000..511e98c87 --- /dev/null +++ b/langfuse/api/prompts/types/placeholder_message_type.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class PlaceholderMessageType(enum.StrEnum): + PLACEHOLDER = "placeholder" + + def visit(self, placeholder: typing.Callable[[], T_Result]) -> T_Result: + if self is PlaceholderMessageType.PLACEHOLDER: + return placeholder() diff --git a/langfuse/api/prompts/types/prompt.py b/langfuse/api/prompts/types/prompt.py new file mode 100644 index 000000000..813e5992f --- /dev/null +++ b/langfuse/api/prompts/types/prompt.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .chat_message_with_placeholders import ChatMessageWithPlaceholders + + +class Prompt_Chat(UniversalBaseModel): + type: typing.Literal["chat"] = "chat" + prompt: typing.List[ChatMessageWithPlaceholders] + name: str + version: int + config: typing.Any + labels: typing.List[str] + tags: typing.List[str] + commit_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="commitMessage") + ] = None + resolution_graph: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Any]], + FieldMetadata(alias="resolutionGraph"), + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class Prompt_Text(UniversalBaseModel): + type: typing.Literal["text"] = "text" + prompt: str + name: str + version: int + config: typing.Any + labels: typing.List[str] + tags: typing.List[str] + commit_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="commitMessage") + ] = None + resolution_graph: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Any]], + FieldMetadata(alias="resolutionGraph"), + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +Prompt = typing_extensions.Annotated[ + typing.Union[Prompt_Chat, Prompt_Text], pydantic.Field(discriminator="type") +] diff --git a/langfuse/api/prompts/types/prompt_meta.py b/langfuse/api/prompts/types/prompt_meta.py new file mode 100644 index 000000000..974a50252 --- /dev/null +++ b/langfuse/api/prompts/types/prompt_meta.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .prompt_type import PromptType + + +class PromptMeta(UniversalBaseModel): + name: str + type: PromptType = pydantic.Field() + """ + Indicates whether the prompt is a text or chat prompt. + """ + + versions: typing.List[int] + labels: typing.List[str] + tags: typing.List[str] + last_updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="lastUpdatedAt") + ] + last_config: typing_extensions.Annotated[ + typing.Any, FieldMetadata(alias="lastConfig") + ] = pydantic.Field() + """ + Config object of the most recent prompt version that matches the filters (if any are provided) + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/prompts/types/prompt_meta_list_response.py b/langfuse/api/prompts/types/prompt_meta_list_response.py new file mode 100644 index 000000000..22043d008 --- /dev/null +++ b/langfuse/api/prompts/types/prompt_meta_list_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse +from .prompt_meta import PromptMeta + + +class PromptMetaListResponse(UniversalBaseModel): + data: typing.List[PromptMeta] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/resources/prompts/types/prompt_type.py b/langfuse/api/prompts/types/prompt_type.py similarity index 87% rename from langfuse/api/resources/prompts/types/prompt_type.py rename to langfuse/api/prompts/types/prompt_type.py index 958d544a6..7d8230db2 100644 --- a/langfuse/api/resources/prompts/types/prompt_type.py +++ b/langfuse/api/prompts/types/prompt_type.py @@ -1,12 +1,13 @@ # This file was auto-generated by Fern from our API Definition. -import enum import typing +from ...core import enum + T_Result = typing.TypeVar("T_Result") -class PromptType(str, enum.Enum): +class PromptType(enum.StrEnum): CHAT = "chat" TEXT = "text" diff --git a/langfuse/api/prompts/types/text_prompt.py b/langfuse/api/prompts/types/text_prompt.py new file mode 100644 index 000000000..fbc53c2f5 --- /dev/null +++ b/langfuse/api/prompts/types/text_prompt.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_prompt import BasePrompt + + +class TextPrompt(BasePrompt): + prompt: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md deleted file mode 100644 index 8a8dad23c..000000000 --- a/langfuse/api/reference.md +++ /dev/null @@ -1,8141 +0,0 @@ -# Reference -## AnnotationQueues -
client.annotation_queues.list_queues(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all annotation queues -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.list_queues() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.create_queue(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create an annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateAnnotationQueueRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.create_queue( - request=CreateAnnotationQueueRequest( - name="name", - score_config_ids=["scoreConfigIds", "scoreConfigIds"], - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateAnnotationQueueRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.get_queue(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get an annotation queue by ID -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.get_queue( - queue_id="queueId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.list_queue_items(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get items for a specific annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.list_queue_items( - queue_id="queueId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**status:** `typing.Optional[AnnotationQueueStatus]` — Filter by status - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.get_queue_item(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a specific item from an annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.get_queue_item( - queue_id="queueId", - item_id="itemId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**item_id:** `str` — The unique identifier of the annotation queue item - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.create_queue_item(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Add an item to an annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.create_queue_item( - queue_id="queueId", - request=CreateAnnotationQueueItemRequest( - object_id="objectId", - object_type=AnnotationQueueObjectType.TRACE, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**request:** `CreateAnnotationQueueItemRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.update_queue_item(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Update an annotation queue item -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import UpdateAnnotationQueueItemRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.update_queue_item( - queue_id="queueId", - item_id="itemId", - request=UpdateAnnotationQueueItemRequest(), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**item_id:** `str` — The unique identifier of the annotation queue item - -
-
- -
-
- -**request:** `UpdateAnnotationQueueItemRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.delete_queue_item(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Remove an item from an annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.delete_queue_item( - queue_id="queueId", - item_id="itemId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**item_id:** `str` — The unique identifier of the annotation queue item - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.create_queue_assignment(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create an assignment for a user to an annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import AnnotationQueueAssignmentRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.create_queue_assignment( - queue_id="queueId", - request=AnnotationQueueAssignmentRequest( - user_id="userId", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**request:** `AnnotationQueueAssignmentRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.annotation_queues.delete_queue_assignment(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete an assignment for a user to an annotation queue -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import AnnotationQueueAssignmentRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.annotation_queues.delete_queue_assignment( - queue_id="queueId", - request=AnnotationQueueAssignmentRequest( - user_id="userId", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**queue_id:** `str` — The unique identifier of the annotation queue - -
-
- -
-
- -**request:** `AnnotationQueueAssignmentRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## BlobStorageIntegrations -
client.blob_storage_integrations.get_blob_storage_integrations() -
-
- -#### 📝 Description - -
-
- -
-
- -Get all blob storage integrations for the organization (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.blob_storage_integrations.get_blob_storage_integrations() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.blob_storage_integrations.upsert_blob_storage_integration(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import ( - BlobStorageExportFrequency, - BlobStorageExportMode, - BlobStorageIntegrationFileType, - BlobStorageIntegrationType, - CreateBlobStorageIntegrationRequest, -) -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.blob_storage_integrations.upsert_blob_storage_integration( - request=CreateBlobStorageIntegrationRequest( - project_id="projectId", - type=BlobStorageIntegrationType.S_3, - bucket_name="bucketName", - region="region", - export_frequency=BlobStorageExportFrequency.HOURLY, - enabled=True, - force_path_style=True, - file_type=BlobStorageIntegrationFileType.JSON, - export_mode=BlobStorageExportMode.FULL_HISTORY, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateBlobStorageIntegrationRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.blob_storage_integrations.delete_blob_storage_integration(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a blob storage integration by ID (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.blob_storage_integrations.delete_blob_storage_integration( - id="id", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Comments -
client.comments.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateCommentRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.comments.create( - request=CreateCommentRequest( - project_id="projectId", - object_type="objectType", - object_id="objectId", - content="content", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateCommentRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.comments.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all comments -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.comments.get() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — Page number, starts at 1. - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit - -
-
- -
-
- -**object_type:** `typing.Optional[str]` — Filter comments by object type (trace, observation, session, prompt). - -
-
- -
-
- -**object_id:** `typing.Optional[str]` — Filter comments by object id. If objectType is not provided, an error will be thrown. - -
-
- -
-
- -**author_user_id:** `typing.Optional[str]` — Filter comments by author user id. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.comments.get_by_id(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a comment by id -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.comments.get_by_id( - comment_id="commentId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**comment_id:** `str` — The unique langfuse identifier of a comment - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## DatasetItems -
client.dataset_items.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a dataset item -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateDatasetItemRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.dataset_items.create( - request=CreateDatasetItemRequest( - dataset_name="datasetName", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateDatasetItemRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.dataset_items.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a dataset item -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.dataset_items.get( - id="id", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.dataset_items.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get dataset items. Optionally specify a version to get the items as they existed at that point in time. -Note: If version parameter is provided, datasetName must also be provided. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.dataset_items.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**dataset_name:** `typing.Optional[str]` - -
-
- -
-
- -**source_trace_id:** `typing.Optional[str]` - -
-
- -
-
- -**source_observation_id:** `typing.Optional[str]` - -
-
- -
-
- -**version:** `typing.Optional[dt.datetime]` - -ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). -If provided, returns state of dataset at this timestamp. -If not provided, returns the latest version. Requires datasetName to be specified. - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.dataset_items.delete(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a dataset item and all its run items. This action is irreversible. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.dataset_items.delete( - id="id", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## DatasetRunItems -
client.dataset_run_items.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a dataset run item -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateDatasetRunItemRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.dataset_run_items.create( - request=CreateDatasetRunItemRequest( - run_name="runName", - dataset_item_id="datasetItemId", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateDatasetRunItemRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.dataset_run_items.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -List dataset run items -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.dataset_run_items.list( - dataset_id="datasetId", - run_name="runName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**dataset_id:** `str` - -
-
- -
-
- -**run_name:** `str` - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Datasets -
client.datasets.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all datasets -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.datasets.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.datasets.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a dataset -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.datasets.get( - dataset_name="datasetName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**dataset_name:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.datasets.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a dataset -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateDatasetRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.datasets.create( - request=CreateDatasetRequest( - name="name", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateDatasetRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.datasets.get_run(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a dataset run and its items -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.datasets.get_run( - dataset_name="datasetName", - run_name="runName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**dataset_name:** `str` - -
-
- -
-
- -**run_name:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.datasets.delete_run(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a dataset run and all its run items. This action is irreversible. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.datasets.delete_run( - dataset_name="datasetName", - run_name="runName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**dataset_name:** `str` - -
-
- -
-
- -**run_name:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.datasets.get_runs(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get dataset runs -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.datasets.get_runs( - dataset_name="datasetName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**dataset_name:** `str` - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Health -
client.health.health() -
-
- -#### 📝 Description - -
-
- -
-
- -Check health of API and database -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.health.health() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Ingestion -
client.ingestion.batch(...) -
-
- -#### 📝 Description - -
-
- -
-
- -**Legacy endpoint for batch ingestion for Langfuse Observability.** - --> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). Learn more: https://langfuse.com/integrations/native/opentelemetry - -Within each batch, there can be multiple events. -Each event has a type, an id, a timestamp, metadata and a body. -Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace. -We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. -The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. -I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. - -Notes: -- Introduction to data model: https://langfuse.com/docs/observability/data-model -- Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. -- The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import IngestionEvent_ScoreCreate, ScoreBody -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.ingestion.batch( - batch=[ - IngestionEvent_ScoreCreate( - id="abcdef-1234-5678-90ab", - timestamp="2022-01-01T00:00:00.000Z", - body=ScoreBody( - id="abcdef-1234-5678-90ab", - trace_id="1234-5678-90ab-cdef", - name="My Score", - value=0.9, - environment="default", - ), - ) - ], -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**batch:** `typing.Sequence[IngestionEvent]` — Batch of tracing events to be ingested. Discriminated by attribute `type`. - -
-
- -
-
- -**metadata:** `typing.Optional[typing.Any]` — Optional. Metadata field used by the Langfuse SDKs for debugging. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## LlmConnections -
client.llm_connections.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all LLM connections in a project -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.llm_connections.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.llm_connections.upsert(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create or update an LLM connection. The connection is upserted on provider. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import LlmAdapter, UpsertLlmConnectionRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.llm_connections.upsert( - request=UpsertLlmConnectionRequest( - provider="provider", - adapter=LlmAdapter.ANTHROPIC, - secret_key="secretKey", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `UpsertLlmConnectionRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Media -
client.media.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a media record -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.media.get( - media_id="mediaId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**media_id:** `str` — The unique langfuse identifier of a media record - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.media.patch(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Patch a media record -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -import datetime - -from langfuse import PatchMediaBody -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.media.patch( - media_id="mediaId", - request=PatchMediaBody( - uploaded_at=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - upload_http_status=1, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**media_id:** `str` — The unique langfuse identifier of a media record - -
-
- -
-
- -**request:** `PatchMediaBody` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.media.get_upload_url(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a presigned upload URL for a media record -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import GetMediaUploadUrlRequest, MediaContentType -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.media.get_upload_url( - request=GetMediaUploadUrlRequest( - trace_id="traceId", - content_type=MediaContentType.IMAGE_PNG, - content_length=1, - sha_256_hash="sha256Hash", - field="field", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `GetMediaUploadUrlRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## MetricsV2 -
client.metrics_v_2.metrics(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - -## V2 Differences -- Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) -- Direct access to tags and release fields on observations -- Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view -- High cardinality dimensions are not supported and will return a 400 error (see below) - -For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - -## Available Views - -### observations -Query observation-level data (spans, generations, events). - -**Dimensions:** -- `environment` - Deployment environment (e.g., production, staging) -- `type` - Type of observation (SPAN, GENERATION, EVENT) -- `name` - Name of the observation -- `level` - Logging level of the observation -- `version` - Version of the observation -- `tags` - User-defined tags -- `release` - Release version -- `traceName` - Name of the parent trace (backwards-compatible) -- `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) -- `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) -- `providedModelName` - Name of the model used -- `promptName` - Name of the prompt used -- `promptVersion` - Version of the prompt used -- `startTimeMonth` - Month of start_time in YYYY-MM format - -**Measures:** -- `count` - Total number of observations -- `latency` - Observation latency (milliseconds) -- `streamingLatency` - Generation latency from completion start to end (milliseconds) -- `inputTokens` - Sum of input tokens consumed -- `outputTokens` - Sum of output tokens produced -- `totalTokens` - Sum of all tokens consumed -- `outputTokensPerSecond` - Output tokens per second -- `tokensPerSecond` - Total tokens per second -- `inputCost` - Input cost (USD) -- `outputCost` - Output cost (USD) -- `totalCost` - Total cost (USD) -- `timeToFirstToken` - Time to first token (milliseconds) -- `countScores` - Number of scores attached to the observation - -### scores-numeric -Query numeric and boolean score data. - -**Dimensions:** -- `environment` - Deployment environment -- `name` - Name of the score (e.g., accuracy, toxicity) -- `source` - Origin of the score (API, ANNOTATION, EVAL) -- `dataType` - Data type (NUMERIC, BOOLEAN) -- `configId` - Identifier of the score config -- `timestampMonth` - Month in YYYY-MM format -- `timestampDay` - Day in YYYY-MM-DD format -- `value` - Numeric value of the score -- `traceName` - Name of the parent trace -- `tags` - Tags -- `traceRelease` - Release version -- `traceVersion` - Version -- `observationName` - Name of the associated observation -- `observationModelName` - Model name of the associated observation -- `observationPromptName` - Prompt name of the associated observation -- `observationPromptVersion` - Prompt version of the associated observation - -**Measures:** -- `count` - Total number of scores -- `value` - Score value (for aggregations) - -### scores-categorical -Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - -**Measures:** -- `count` - Total number of scores - -## High Cardinality Dimensions -The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. -Use them in filters instead. - -**observations view:** -- `id` - Use traceId filter to narrow down results -- `traceId` - Use traceId filter instead -- `userId` - Use userId filter instead -- `sessionId` - Use sessionId filter instead -- `parentObservationId` - Use parentObservationId filter instead - -**scores-numeric / scores-categorical views:** -- `id` - Use specific filters to narrow down results -- `traceId` - Use traceId filter instead -- `userId` - Use userId filter instead -- `sessionId` - Use sessionId filter instead -- `observationId` - Use observationId filter instead - -## Aggregations -Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - -## Time Granularities -Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` -- `auto` bins the data into approximately 50 buckets based on the time range -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.metrics_v_2.metrics( - query="query", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `str` - -JSON string containing the query parameters with the following structure: -```json -{ - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } -} -``` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Metrics -
client.metrics.metrics(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get metrics from the Langfuse project using a query object. - -Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. - -For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.metrics.metrics( - query="query", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**query:** `str` - -JSON string containing the query parameters with the following structure: -```json -{ - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" - "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) - } -} -``` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Models -
client.models.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a model -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateModelRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.models.create( - request=CreateModelRequest( - model_name="modelName", - match_pattern="matchPattern", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateModelRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.models.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all models -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.models.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.models.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a model -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.models.get( - id="id", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.models.delete(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.models.delete( - id="id", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## ObservationsV2 -
client.observations_v_2.get_many(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a list of observations with cursor-based pagination and flexible field selection. - -## Cursor-based Pagination -This endpoint uses cursor-based pagination for efficient traversal of large datasets. -The cursor is returned in the response metadata and should be passed in subsequent requests -to retrieve the next page of results. - -## Field Selection -Use the `fields` parameter to control which observation fields are returned: -- `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type -- `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId -- `time` - completionStartTime, createdAt, updatedAt -- `io` - input, output -- `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) -- `model` - providedModelName, internalModelId, modelParameters -- `usage` - usageDetails, costDetails, totalCost -- `prompt` - promptId, promptName, promptVersion -- `metrics` - latency, timeToFirstToken - -If not specified, `core` and `basic` field groups are returned. - -## Filters -Multiple filtering options are available via query parameters or the structured `filter` parameter. -When using the `filter` parameter, it takes precedence over individual query parameter filters. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.observations_v_2.get_many() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**fields:** `typing.Optional[str]` - -Comma-separated list of field groups to include in the response. -Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. -If not specified, `core` and `basic` field groups are returned. -Example: "basic,usage,model" - -
-
- -
-
- -**expand_metadata:** `typing.Optional[str]` - -Comma-separated list of metadata keys to return non-truncated. -By default, metadata values over 200 characters are truncated. -Use this parameter to retrieve full values for specific keys. -Example: "key1,key2" - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Number of items to return per page. Maximum 1000, default 50. - -
-
- -
-
- -**cursor:** `typing.Optional[str]` — Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - -
-
- -
-
- -**parse_io_as_json:** `typing.Optional[bool]` - -Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. -Defaults to `false` if not provided. - -
-
- -
-
- -**name:** `typing.Optional[str]` - -
-
- -
-
- -**user_id:** `typing.Optional[str]` - -
-
- -
-
- -**type:** `typing.Optional[str]` — Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") - -
-
- -
-
- -**trace_id:** `typing.Optional[str]` - -
-
- -
-
- -**level:** `typing.Optional[ObservationLevel]` — Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). - -
-
- -
-
- -**parent_observation_id:** `typing.Optional[str]` - -
-
- -
-
- -**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for observations where the environment is one of the provided values. - -
-
- -
-
- -**from_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time on or after this datetime (ISO 8601). - -
-
- -
-
- -**to_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time before this datetime (ISO 8601). - -
-
- -
-
- -**version:** `typing.Optional[str]` — Optional filter to only include observations with a certain version. - -
-
- -
-
- -**filter:** `typing.Optional[str]` - -JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). - -## Filter Structure -Each filter condition has the following structure: -```json -[ - { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on (see available columns below) - "operator": string, // Required. Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" - // - numberObject: "=", ">", "<", ">=", "<=" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata - } -] -``` - -## Available Columns - -### Core Observation Fields -- `id` (string) - Observation ID -- `type` (string) - Observation type (SPAN, GENERATION, EVENT) -- `name` (string) - Observation name -- `traceId` (string) - Associated trace ID -- `startTime` (datetime) - Observation start time -- `endTime` (datetime) - Observation end time -- `environment` (string) - Environment tag -- `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) -- `statusMessage` (string) - Status message -- `version` (string) - Version tag -- `userId` (string) - User ID -- `sessionId` (string) - Session ID - -### Trace-Related Fields -- `traceName` (string) - Name of the parent trace -- `traceTags` (arrayOptions) - Tags from the parent trace -- `tags` (arrayOptions) - Alias for traceTags - -### Performance Metrics -- `latency` (number) - Latency in seconds (calculated: end_time - start_time) -- `timeToFirstToken` (number) - Time to first token in seconds -- `tokensPerSecond` (number) - Output tokens per second - -### Token Usage -- `inputTokens` (number) - Number of input tokens -- `outputTokens` (number) - Number of output tokens -- `totalTokens` (number) - Total tokens (alias: `tokens`) - -### Cost Metrics -- `inputCost` (number) - Input cost in USD -- `outputCost` (number) - Output cost in USD -- `totalCost` (number) - Total cost in USD - -### Model Information -- `model` (string) - Provided model name (alias: `providedModelName`) -- `promptName` (string) - Associated prompt name -- `promptVersion` (number) - Associated prompt version - -### Structured Data -- `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - -## Filter Examples -```json -[ - { - "type": "string", - "column": "type", - "operator": "=", - "value": "GENERATION" - }, - { - "type": "number", - "column": "latency", - "operator": ">=", - "value": 2.5 - }, - { - "type": "stringObject", - "column": "metadata", - "key": "environment", - "operator": "=", - "value": "production" - } -] -``` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Observations -
client.observations.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a observation -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.observations.get( - observation_id="observationId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**observation_id:** `str` — The unique langfuse identifier of an observation, can be an event, span or generation - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.observations.get_many(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a list of observations. - -Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.observations.get_many() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — Page number, starts at 1. - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. - -
-
- -
-
- -**name:** `typing.Optional[str]` - -
-
- -
-
- -**user_id:** `typing.Optional[str]` - -
-
- -
-
- -**type:** `typing.Optional[str]` - -
-
- -
-
- -**trace_id:** `typing.Optional[str]` - -
-
- -
-
- -**level:** `typing.Optional[ObservationLevel]` — Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR"). - -
-
- -
-
- -**parent_observation_id:** `typing.Optional[str]` - -
-
- -
-
- -**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for observations where the environment is one of the provided values. - -
-
- -
-
- -**from_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time on or after this datetime (ISO 8601). - -
-
- -
-
- -**to_start_time:** `typing.Optional[dt.datetime]` — Retrieve only observations with a start_time before this datetime (ISO 8601). - -
-
- -
-
- -**version:** `typing.Optional[str]` — Optional filter to only include observations with a certain version. - -
-
- -
-
- -**filter:** `typing.Optional[str]` - -JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). - -## Filter Structure -Each filter condition has the following structure: -```json -[ - { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on (see available columns below) - "operator": string, // Required. Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" - // - numberObject: "=", ">", "<", ">=", "<=" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata - } -] -``` - -## Available Columns - -### Core Observation Fields -- `id` (string) - Observation ID -- `type` (string) - Observation type (SPAN, GENERATION, EVENT) -- `name` (string) - Observation name -- `traceId` (string) - Associated trace ID -- `startTime` (datetime) - Observation start time -- `endTime` (datetime) - Observation end time -- `environment` (string) - Environment tag -- `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) -- `statusMessage` (string) - Status message -- `version` (string) - Version tag - -### Performance Metrics -- `latency` (number) - Latency in seconds (calculated: end_time - start_time) -- `timeToFirstToken` (number) - Time to first token in seconds -- `tokensPerSecond` (number) - Output tokens per second - -### Token Usage -- `inputTokens` (number) - Number of input tokens -- `outputTokens` (number) - Number of output tokens -- `totalTokens` (number) - Total tokens (alias: `tokens`) - -### Cost Metrics -- `inputCost` (number) - Input cost in USD -- `outputCost` (number) - Output cost in USD -- `totalCost` (number) - Total cost in USD - -### Model Information -- `model` (string) - Provided model name -- `promptName` (string) - Associated prompt name -- `promptVersion` (number) - Associated prompt version - -### Structured Data -- `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - -### Associated Trace Fields (requires join with traces table) -- `userId` (string) - User ID from associated trace -- `traceName` (string) - Name from associated trace -- `traceEnvironment` (string) - Environment from associated trace -- `traceTags` (arrayOptions) - Tags from associated trace - -## Filter Examples -```json -[ - { - "type": "string", - "column": "type", - "operator": "=", - "value": "GENERATION" - }, - { - "type": "number", - "column": "latency", - "operator": ">=", - "value": 2.5 - }, - { - "type": "stringObject", - "column": "metadata", - "key": "environment", - "operator": "=", - "value": "production" - } -] -``` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Opentelemetry -
client.opentelemetry.export_traces(...) -
-
- -#### 📝 Description - -
-
- -
-
- -**OpenTelemetry Traces Ingestion Endpoint** - -This endpoint implements the OTLP/HTTP specification for trace ingestion, providing native OpenTelemetry integration for Langfuse Observability. - -**Supported Formats:** -- Binary Protobuf: `Content-Type: application/x-protobuf` -- JSON Protobuf: `Content-Type: application/json` -- Supports gzip compression via `Content-Encoding: gzip` header - -**Specification Compliance:** -- Conforms to [OTLP/HTTP Trace Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) -- Implements `ExportTraceServiceRequest` message format - -**Documentation:** -- Integration guide: https://langfuse.com/integrations/native/opentelemetry -- Data model: https://langfuse.com/docs/observability/data-model -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import ( - OtelAttribute, - OtelAttributeValue, - OtelResource, - OtelResourceSpan, - OtelScope, - OtelScopeSpan, - OtelSpan, -) -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.opentelemetry.export_traces( - resource_spans=[ - OtelResourceSpan( - resource=OtelResource( - attributes=[ - OtelAttribute( - key="service.name", - value=OtelAttributeValue( - string_value="my-service", - ), - ), - OtelAttribute( - key="service.version", - value=OtelAttributeValue( - string_value="1.0.0", - ), - ), - ], - ), - scope_spans=[ - OtelScopeSpan( - scope=OtelScope( - name="langfuse-sdk", - version="2.60.3", - ), - spans=[ - OtelSpan( - trace_id="0123456789abcdef0123456789abcdef", - span_id="0123456789abcdef", - name="my-operation", - kind=1, - start_time_unix_nano="1747872000000000000", - end_time_unix_nano="1747872001000000000", - attributes=[ - OtelAttribute( - key="langfuse.observation.type", - value=OtelAttributeValue( - string_value="generation", - ), - ) - ], - status={}, - ) - ], - ) - ], - ) - ], -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**resource_spans:** `typing.Sequence[OtelResourceSpan]` — Array of resource spans containing trace data as defined in the OTLP specification - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Organizations -
client.organizations.get_organization_memberships() -
-
- -#### 📝 Description - -
-
- -
-
- -Get all memberships for the organization associated with the API key (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.get_organization_memberships() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.update_organization_membership(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create or update a membership for the organization associated with the API key (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import MembershipRequest, MembershipRole -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.update_organization_membership( - request=MembershipRequest( - user_id="userId", - role=MembershipRole.OWNER, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `MembershipRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.delete_organization_membership(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a membership from the organization associated with the API key (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import DeleteMembershipRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.delete_organization_membership( - request=DeleteMembershipRequest( - user_id="userId", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `DeleteMembershipRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.get_project_memberships(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all memberships for a specific project (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.get_project_memberships( - project_id="projectId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.update_project_membership(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import MembershipRequest, MembershipRole -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.update_project_membership( - project_id="projectId", - request=MembershipRequest( - user_id="userId", - role=MembershipRole.OWNER, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**request:** `MembershipRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.delete_project_membership(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import DeleteMembershipRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.delete_project_membership( - project_id="projectId", - request=DeleteMembershipRequest( - user_id="userId", - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**request:** `DeleteMembershipRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.get_organization_projects() -
-
- -#### 📝 Description - -
-
- -
-
- -Get all projects for the organization associated with the API key (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.get_organization_projects() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.organizations.get_organization_api_keys() -
-
- -#### 📝 Description - -
-
- -
-
- -Get all API keys for the organization associated with the API key (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.organizations.get_organization_api_keys() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Projects -
client.projects.get() -
-
- -#### 📝 Description - -
-
- -
-
- -Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.get() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.projects.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a new project (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.create( - name="name", - retention=1, -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `str` - -
-
- -
-
- -**retention:** `int` — Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. - -
-
- -
-
- -**metadata:** `typing.Optional[typing.Dict[str, typing.Any]]` — Optional metadata for the project - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.projects.update(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Update a project by ID (requires organization-scoped API key). -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.update( - project_id="projectId", - name="name", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**name:** `str` - -
-
- -
-
- -**metadata:** `typing.Optional[typing.Dict[str, typing.Any]]` — Optional metadata for the project - -
-
- -
-
- -**retention:** `typing.Optional[int]` - -Number of days to retain data. -Must be 0 or at least 3 days. -Requires data-retention entitlement for non-zero values. -Optional. Will retain existing retention setting if omitted. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.projects.delete(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.delete( - project_id="projectId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.projects.get_api_keys(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all API keys for a project (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.get_api_keys( - project_id="projectId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.projects.create_api_key(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a new API key for a project (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.create_api_key( - project_id="projectId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**note:** `typing.Optional[str]` — Optional note for the API key - -
-
- -
-
- -**public_key:** `typing.Optional[str]` — Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. - -
-
- -
-
- -**secret_key:** `typing.Optional[str]` — Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.projects.delete_api_key(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete an API key for a project (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.projects.delete_api_key( - project_id="projectId", - api_key_id="apiKeyId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**project_id:** `str` - -
-
- -
-
- -**api_key_id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## PromptVersion -
client.prompt_version.update(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Update labels for a specific prompt version -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.prompt_version.update( - name="name", - version=1, - new_labels=["newLabels", "newLabels"], -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `str` - -The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), -the folder path must be URL encoded. - -
-
- -
-
- -**version:** `int` — Version of the prompt to update - -
-
- -
-
- -**new_labels:** `typing.Sequence[str]` — New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Prompts -
client.prompts.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a prompt -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.prompts.get( - prompt_name="promptName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**prompt_name:** `str` - -The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), -the folder path must be URL encoded. - -
-
- -
-
- -**version:** `typing.Optional[int]` — Version of the prompt to be retrieved. - -
-
- -
-
- -**label:** `typing.Optional[str]` — Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.prompts.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a list of prompt names with versions and labels -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.prompts.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**name:** `typing.Optional[str]` - -
-
- -
-
- -**label:** `typing.Optional[str]` - -
-
- -
-
- -**tag:** `typing.Optional[str]` - -
-
- -
-
- -**page:** `typing.Optional[int]` — page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — limit of items per page - -
-
- -
-
- -**from_updated_at:** `typing.Optional[dt.datetime]` — Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) - -
-
- -
-
- -**to_updated_at:** `typing.Optional[dt.datetime]` — Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.prompts.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a new version for the prompt with the given `name` -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import ( - ChatMessageWithPlaceholders_Chatmessage, - CreatePromptRequest_Chat, -) -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.prompts.create( - request=CreatePromptRequest_Chat( - name="name", - prompt=[ - ChatMessageWithPlaceholders_Chatmessage( - role="role", - content="content", - ), - ChatMessageWithPlaceholders_Chatmessage( - role="role", - content="content", - ), - ], - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreatePromptRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.prompts.delete(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.prompts.delete( - prompt_name="promptName", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**prompt_name:** `str` — The name of the prompt - -
-
- -
-
- -**label:** `typing.Optional[str]` — Optional label to filter deletion. If specified, deletes all prompt versions that have this label. - -
-
- -
-
- -**version:** `typing.Optional[int]` — Optional version to filter deletion. If specified, deletes only this specific version of the prompt. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Scim -
client.scim.get_service_provider_config() -
-
- -#### 📝 Description - -
-
- -
-
- -Get SCIM Service Provider Configuration (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.get_service_provider_config() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.scim.get_resource_types() -
-
- -#### 📝 Description - -
-
- -
-
- -Get SCIM Resource Types (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.get_resource_types() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.scim.get_schemas() -
-
- -#### 📝 Description - -
-
- -
-
- -Get SCIM Schemas (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.get_schemas() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.scim.list_users(...) -
-
- -#### 📝 Description - -
-
- -
-
- -List users in the organization (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.list_users() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**filter:** `typing.Optional[str]` — Filter expression (e.g. userName eq "value") - -
-
- -
-
- -**start_index:** `typing.Optional[int]` — 1-based index of the first result to return (default 1) - -
-
- -
-
- -**count:** `typing.Optional[int]` — Maximum number of results to return (default 100) - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.scim.create_user(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a new user in the organization (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import ScimName -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.create_user( - user_name="userName", - name=ScimName(), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**user_name:** `str` — User's email address (required) - -
-
- -
-
- -**name:** `ScimName` — User's name information - -
-
- -
-
- -**emails:** `typing.Optional[typing.Sequence[ScimEmail]]` — User's email addresses - -
-
- -
-
- -**active:** `typing.Optional[bool]` — Whether the user is active - -
-
- -
-
- -**password:** `typing.Optional[str]` — Initial password for the user - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.scim.get_user(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a specific user by ID (requires organization-scoped API key) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.get_user( - user_id="userId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**user_id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.scim.delete_user(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.scim.delete_user( - user_id="userId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**user_id:** `str` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## ScoreConfigs -
client.score_configs.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a score configuration (config). Score configs are used to define the structure of scores -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateScoreConfigRequest, ScoreConfigDataType -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score_configs.create( - request=CreateScoreConfigRequest( - name="name", - data_type=ScoreConfigDataType.NUMERIC, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateScoreConfigRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.score_configs.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get all score configs -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score_configs.get() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — Page number, starts at 1. - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.score_configs.get_by_id(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a score config -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score_configs.get_by_id( - config_id="configId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**config_id:** `str` — The unique langfuse identifier of a score config - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.score_configs.update(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Update a score config -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import UpdateScoreConfigRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score_configs.update( - config_id="configId", - request=UpdateScoreConfigRequest(), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**config_id:** `str` — The unique langfuse identifier of a score config - -
-
- -
-
- -**request:** `UpdateScoreConfigRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## ScoreV2 -
client.score_v_2.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a list of scores (supports both trace and session scores) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score_v_2.get() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — Page number, starts at 1. - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. - -
-
- -
-
- -**user_id:** `typing.Optional[str]` — Retrieve only scores with this userId associated to the trace. - -
-
- -
-
- -**name:** `typing.Optional[str]` — Retrieve only scores with this name. - -
-
- -
-
- -**from_timestamp:** `typing.Optional[dt.datetime]` — Optional filter to only include scores created on or after a certain datetime (ISO 8601) - -
-
- -
-
- -**to_timestamp:** `typing.Optional[dt.datetime]` — Optional filter to only include scores created before a certain datetime (ISO 8601) - -
-
- -
-
- -**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for scores where the environment is one of the provided values. - -
-
- -
-
- -**source:** `typing.Optional[ScoreSource]` — Retrieve only scores from a specific source. - -
-
- -
-
- -**operator:** `typing.Optional[str]` — Retrieve only scores with value. - -
-
- -
-
- -**value:** `typing.Optional[float]` — Retrieve only scores with value. - -
-
- -
-
- -**score_ids:** `typing.Optional[str]` — Comma-separated list of score IDs to limit the results to. - -
-
- -
-
- -**config_id:** `typing.Optional[str]` — Retrieve only scores with a specific configId. - -
-
- -
-
- -**session_id:** `typing.Optional[str]` — Retrieve only scores with a specific sessionId. - -
-
- -
-
- -**dataset_run_id:** `typing.Optional[str]` — Retrieve only scores with a specific datasetRunId. - -
-
- -
-
- -**trace_id:** `typing.Optional[str]` — Retrieve only scores with a specific traceId. - -
-
- -
-
- -**observation_id:** `typing.Optional[str]` — Comma-separated list of observation IDs to filter scores by. - -
-
- -
-
- -**queue_id:** `typing.Optional[str]` — Retrieve only scores with a specific annotation queueId. - -
-
- -
-
- -**data_type:** `typing.Optional[ScoreDataType]` — Retrieve only scores with a specific dataType. - -
-
- -
-
- -**trace_tags:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Only scores linked to traces that include all of these tags will be returned. - -
-
- -
-
- -**fields:** `typing.Optional[str]` — Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.score_v_2.get_by_id(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a score (supports both trace and session scores) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score_v_2.get_by_id( - score_id="scoreId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**score_id:** `str` — The unique langfuse identifier of a score - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Score -
client.score.create(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Create a score (supports both trace and session scores) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse import CreateScoreRequest -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score.create( - request=CreateScoreRequest( - name="name", - value=1.1, - ), -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**request:** `CreateScoreRequest` - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.score.delete(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a score (supports both trace and session scores) -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.score.delete( - score_id="scoreId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**score_id:** `str` — The unique langfuse identifier of a score - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Sessions -
client.sessions.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get sessions -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.sessions.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — Page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. - -
-
- -
-
- -**from_timestamp:** `typing.Optional[dt.datetime]` — Optional filter to only include sessions created on or after a certain datetime (ISO 8601) - -
-
- -
-
- -**to_timestamp:** `typing.Optional[dt.datetime]` — Optional filter to only include sessions created before a certain datetime (ISO 8601) - -
-
- -
-
- -**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for sessions where the environment is one of the provided values. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.sessions.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.sessions.get( - session_id="sessionId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**session_id:** `str` — The unique id of a session - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -## Trace -
client.trace.get(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get a specific trace -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.trace.get( - trace_id="traceId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**trace_id:** `str` — The unique langfuse identifier of a trace - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.trace.delete(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete a specific trace -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.trace.delete( - trace_id="traceId", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**trace_id:** `str` — The unique langfuse identifier of the trace to delete - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.trace.list(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Get list of traces -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.trace.list() - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**page:** `typing.Optional[int]` — Page number, starts at 1 - -
-
- -
-
- -**limit:** `typing.Optional[int]` — Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. - -
-
- -
-
- -**user_id:** `typing.Optional[str]` - -
-
- -
-
- -**name:** `typing.Optional[str]` - -
-
- -
-
- -**session_id:** `typing.Optional[str]` - -
-
- -
-
- -**from_timestamp:** `typing.Optional[dt.datetime]` — Optional filter to only include traces with a trace.timestamp on or after a certain datetime (ISO 8601) - -
-
- -
-
- -**to_timestamp:** `typing.Optional[dt.datetime]` — Optional filter to only include traces with a trace.timestamp before a certain datetime (ISO 8601) - -
-
- -
-
- -**order_by:** `typing.Optional[str]` — Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc - -
-
- -
-
- -**tags:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Only traces that include all of these tags will be returned. - -
-
- -
-
- -**version:** `typing.Optional[str]` — Optional filter to only include traces with a certain version. - -
-
- -
-
- -**release:** `typing.Optional[str]` — Optional filter to only include traces with a certain release. - -
-
- -
-
- -**environment:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Optional filter for traces where the environment is one of the provided values. - -
-
- -
-
- -**fields:** `typing.Optional[str]` — Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. - -
-
- -
-
- -**filter:** `typing.Optional[str]` - -JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). - -## Filter Structure -Each filter condition has the following structure: -```json -[ - { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on (see available columns below) - "operator": string, // Required. Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" - // - numberObject: "=", ">", "<", ">=", "<=" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata - } -] -``` - -## Available Columns - -### Core Trace Fields -- `id` (string) - Trace ID -- `name` (string) - Trace name -- `timestamp` (datetime) - Trace timestamp -- `userId` (string) - User ID -- `sessionId` (string) - Session ID -- `environment` (string) - Environment tag -- `version` (string) - Version tag -- `release` (string) - Release tag -- `tags` (arrayOptions) - Array of tags -- `bookmarked` (boolean) - Bookmark status - -### Structured Data -- `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - -### Aggregated Metrics (from observations) -These metrics are aggregated from all observations within the trace: -- `latency` (number) - Latency in seconds (time from first observation start to last observation end) -- `inputTokens` (number) - Total input tokens across all observations -- `outputTokens` (number) - Total output tokens across all observations -- `totalTokens` (number) - Total tokens (alias: `tokens`) -- `inputCost` (number) - Total input cost in USD -- `outputCost` (number) - Total output cost in USD -- `totalCost` (number) - Total cost in USD - -### Observation Level Aggregations -These fields aggregate observation levels within the trace: -- `level` (string) - Highest severity level (ERROR > WARNING > DEFAULT > DEBUG) -- `warningCount` (number) - Count of WARNING level observations -- `errorCount` (number) - Count of ERROR level observations -- `defaultCount` (number) - Count of DEFAULT level observations -- `debugCount` (number) - Count of DEBUG level observations - -### Scores (requires join with scores table) -- `scores_avg` (number) - Average of numeric scores (alias: `scores`) -- `score_categories` (categoryOptions) - Categorical score values - -## Filter Examples -```json -[ - { - "type": "datetime", - "column": "timestamp", - "operator": ">=", - "value": "2024-01-01T00:00:00Z" - }, - { - "type": "string", - "column": "userId", - "operator": "=", - "value": "user-123" - }, - { - "type": "number", - "column": "totalCost", - "operator": ">=", - "value": 0.01 - }, - { - "type": "arrayOptions", - "column": "tags", - "operator": "all of", - "value": ["production", "critical"] - }, - { - "type": "stringObject", - "column": "metadata", - "key": "customer_tier", - "operator": "=", - "value": "enterprise" - } -] -``` - -## Performance Notes -- Filtering on `userId`, `sessionId`, or `metadata` may enable skip indexes for better query performance -- Score filters require a join with the scores table and may impact query performance - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- -
client.trace.delete_multiple(...) -
-
- -#### 📝 Description - -
-
- -
-
- -Delete multiple traces -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from langfuse.client import FernLangfuse - -client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", -) -client.trace.delete_multiple( - trace_ids=["traceIds", "traceIds"], -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**trace_ids:** `typing.Sequence[str]` — List of trace IDs to delete - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
-
-
- - -
-
-
- diff --git a/langfuse/api/resources/__init__.py b/langfuse/api/resources/__init__.py deleted file mode 100644 index 0de0a56a5..000000000 --- a/langfuse/api/resources/__init__.py +++ /dev/null @@ -1,543 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from . import ( - annotation_queues, - blob_storage_integrations, - comments, - commons, - dataset_items, - dataset_run_items, - datasets, - health, - ingestion, - llm_connections, - media, - metrics, - metrics_v_2, - models, - observations, - observations_v_2, - opentelemetry, - organizations, - projects, - prompt_version, - prompts, - scim, - score, - score_configs, - score_v_2, - sessions, - trace, - utils, -) -from .annotation_queues import ( - AnnotationQueue, - AnnotationQueueAssignmentRequest, - AnnotationQueueItem, - AnnotationQueueObjectType, - AnnotationQueueStatus, - CreateAnnotationQueueAssignmentResponse, - CreateAnnotationQueueItemRequest, - CreateAnnotationQueueRequest, - DeleteAnnotationQueueAssignmentResponse, - DeleteAnnotationQueueItemResponse, - PaginatedAnnotationQueueItems, - PaginatedAnnotationQueues, - UpdateAnnotationQueueItemRequest, -) -from .blob_storage_integrations import ( - BlobStorageExportFrequency, - BlobStorageExportMode, - BlobStorageIntegrationDeletionResponse, - BlobStorageIntegrationFileType, - BlobStorageIntegrationResponse, - BlobStorageIntegrationType, - BlobStorageIntegrationsResponse, - CreateBlobStorageIntegrationRequest, -) -from .comments import CreateCommentRequest, CreateCommentResponse, GetCommentsResponse -from .commons import ( - AccessDeniedError, - BaseScore, - BaseScoreV1, - BooleanScore, - BooleanScoreV1, - CategoricalScore, - CategoricalScoreV1, - Comment, - CommentObjectType, - ConfigCategory, - CorrectionScore, - CreateScoreValue, - Dataset, - DatasetItem, - DatasetRun, - DatasetRunItem, - DatasetRunWithItems, - DatasetStatus, - Error, - MapValue, - MethodNotAllowedError, - Model, - ModelPrice, - ModelUsageUnit, - NotFoundError, - NumericScore, - NumericScoreV1, - Observation, - ObservationLevel, - ObservationsView, - PricingTier, - PricingTierCondition, - PricingTierInput, - PricingTierOperator, - Score, - ScoreConfig, - ScoreConfigDataType, - ScoreDataType, - ScoreSource, - ScoreV1, - ScoreV1_Boolean, - ScoreV1_Categorical, - ScoreV1_Numeric, - Score_Boolean, - Score_Categorical, - Score_Correction, - Score_Numeric, - Session, - SessionWithTraces, - Trace, - TraceWithDetails, - TraceWithFullDetails, - UnauthorizedError, - Usage, -) -from .dataset_items import ( - CreateDatasetItemRequest, - DeleteDatasetItemResponse, - PaginatedDatasetItems, -) -from .dataset_run_items import CreateDatasetRunItemRequest, PaginatedDatasetRunItems -from .datasets import ( - CreateDatasetRequest, - DeleteDatasetRunResponse, - PaginatedDatasetRuns, - PaginatedDatasets, -) -from .health import HealthResponse, ServiceUnavailableError -from .ingestion import ( - BaseEvent, - CreateEventBody, - CreateEventEvent, - CreateGenerationBody, - CreateGenerationEvent, - CreateObservationEvent, - CreateSpanBody, - CreateSpanEvent, - IngestionError, - IngestionEvent, - IngestionEvent_EventCreate, - IngestionEvent_GenerationCreate, - IngestionEvent_GenerationUpdate, - IngestionEvent_ObservationCreate, - IngestionEvent_ObservationUpdate, - IngestionEvent_ScoreCreate, - IngestionEvent_SdkLog, - IngestionEvent_SpanCreate, - IngestionEvent_SpanUpdate, - IngestionEvent_TraceCreate, - IngestionResponse, - IngestionSuccess, - IngestionUsage, - ObservationBody, - ObservationType, - OpenAiCompletionUsageSchema, - OpenAiResponseUsageSchema, - OpenAiUsage, - OptionalObservationBody, - ScoreBody, - ScoreEvent, - SdkLogBody, - SdkLogEvent, - TraceBody, - TraceEvent, - UpdateEventBody, - UpdateGenerationBody, - UpdateGenerationEvent, - UpdateObservationEvent, - UpdateSpanBody, - UpdateSpanEvent, - UsageDetails, -) -from .llm_connections import ( - LlmAdapter, - LlmConnection, - PaginatedLlmConnections, - UpsertLlmConnectionRequest, -) -from .media import ( - GetMediaResponse, - GetMediaUploadUrlRequest, - GetMediaUploadUrlResponse, - MediaContentType, - PatchMediaBody, -) -from .metrics import MetricsResponse -from .metrics_v_2 import MetricsV2Response -from .models import CreateModelRequest, PaginatedModels -from .observations import Observations, ObservationsViews -from .observations_v_2 import ObservationsV2Meta, ObservationsV2Response -from .opentelemetry import ( - OtelAttribute, - OtelAttributeValue, - OtelResource, - OtelResourceSpan, - OtelScope, - OtelScopeSpan, - OtelSpan, - OtelTraceResponse, -) -from .organizations import ( - DeleteMembershipRequest, - MembershipDeletionResponse, - MembershipRequest, - MembershipResponse, - MembershipRole, - MembershipsResponse, - OrganizationApiKey, - OrganizationApiKeysResponse, - OrganizationProject, - OrganizationProjectsResponse, -) -from .projects import ( - ApiKeyDeletionResponse, - ApiKeyList, - ApiKeyResponse, - ApiKeySummary, - Organization, - Project, - ProjectDeletionResponse, - Projects, -) -from .prompts import ( - BasePrompt, - ChatMessage, - ChatMessageWithPlaceholders, - ChatMessageWithPlaceholders_Chatmessage, - ChatMessageWithPlaceholders_Placeholder, - ChatPrompt, - CreateChatPromptRequest, - CreatePromptRequest, - CreatePromptRequest_Chat, - CreatePromptRequest_Text, - CreateTextPromptRequest, - PlaceholderMessage, - Prompt, - PromptMeta, - PromptMetaListResponse, - PromptType, - Prompt_Chat, - Prompt_Text, - TextPrompt, -) -from .scim import ( - AuthenticationScheme, - BulkConfig, - EmptyResponse, - FilterConfig, - ResourceMeta, - ResourceType, - ResourceTypesResponse, - SchemaExtension, - SchemaResource, - SchemasResponse, - ScimEmail, - ScimFeatureSupport, - ScimName, - ScimUser, - ScimUsersListResponse, - ServiceProviderConfig, - UserMeta, -) -from .score import CreateScoreRequest, CreateScoreResponse -from .score_configs import ( - CreateScoreConfigRequest, - ScoreConfigs, - UpdateScoreConfigRequest, -) -from .score_v_2 import ( - GetScoresResponse, - GetScoresResponseData, - GetScoresResponseDataBoolean, - GetScoresResponseDataCategorical, - GetScoresResponseDataCorrection, - GetScoresResponseDataNumeric, - GetScoresResponseData_Boolean, - GetScoresResponseData_Categorical, - GetScoresResponseData_Correction, - GetScoresResponseData_Numeric, - GetScoresResponseTraceData, -) -from .sessions import PaginatedSessions -from .trace import DeleteTraceResponse, Sort, Traces - -__all__ = [ - "AccessDeniedError", - "AnnotationQueue", - "AnnotationQueueAssignmentRequest", - "AnnotationQueueItem", - "AnnotationQueueObjectType", - "AnnotationQueueStatus", - "ApiKeyDeletionResponse", - "ApiKeyList", - "ApiKeyResponse", - "ApiKeySummary", - "AuthenticationScheme", - "BaseEvent", - "BasePrompt", - "BaseScore", - "BaseScoreV1", - "BlobStorageExportFrequency", - "BlobStorageExportMode", - "BlobStorageIntegrationDeletionResponse", - "BlobStorageIntegrationFileType", - "BlobStorageIntegrationResponse", - "BlobStorageIntegrationType", - "BlobStorageIntegrationsResponse", - "BooleanScore", - "BooleanScoreV1", - "BulkConfig", - "CategoricalScore", - "CategoricalScoreV1", - "ChatMessage", - "ChatMessageWithPlaceholders", - "ChatMessageWithPlaceholders_Chatmessage", - "ChatMessageWithPlaceholders_Placeholder", - "ChatPrompt", - "Comment", - "CommentObjectType", - "ConfigCategory", - "CorrectionScore", - "CreateAnnotationQueueAssignmentResponse", - "CreateAnnotationQueueItemRequest", - "CreateAnnotationQueueRequest", - "CreateBlobStorageIntegrationRequest", - "CreateChatPromptRequest", - "CreateCommentRequest", - "CreateCommentResponse", - "CreateDatasetItemRequest", - "CreateDatasetRequest", - "CreateDatasetRunItemRequest", - "CreateEventBody", - "CreateEventEvent", - "CreateGenerationBody", - "CreateGenerationEvent", - "CreateModelRequest", - "CreateObservationEvent", - "CreatePromptRequest", - "CreatePromptRequest_Chat", - "CreatePromptRequest_Text", - "CreateScoreConfigRequest", - "CreateScoreRequest", - "CreateScoreResponse", - "CreateScoreValue", - "CreateSpanBody", - "CreateSpanEvent", - "CreateTextPromptRequest", - "Dataset", - "DatasetItem", - "DatasetRun", - "DatasetRunItem", - "DatasetRunWithItems", - "DatasetStatus", - "DeleteAnnotationQueueAssignmentResponse", - "DeleteAnnotationQueueItemResponse", - "DeleteDatasetItemResponse", - "DeleteDatasetRunResponse", - "DeleteMembershipRequest", - "DeleteTraceResponse", - "EmptyResponse", - "Error", - "FilterConfig", - "GetCommentsResponse", - "GetMediaResponse", - "GetMediaUploadUrlRequest", - "GetMediaUploadUrlResponse", - "GetScoresResponse", - "GetScoresResponseData", - "GetScoresResponseDataBoolean", - "GetScoresResponseDataCategorical", - "GetScoresResponseDataCorrection", - "GetScoresResponseDataNumeric", - "GetScoresResponseData_Boolean", - "GetScoresResponseData_Categorical", - "GetScoresResponseData_Correction", - "GetScoresResponseData_Numeric", - "GetScoresResponseTraceData", - "HealthResponse", - "IngestionError", - "IngestionEvent", - "IngestionEvent_EventCreate", - "IngestionEvent_GenerationCreate", - "IngestionEvent_GenerationUpdate", - "IngestionEvent_ObservationCreate", - "IngestionEvent_ObservationUpdate", - "IngestionEvent_ScoreCreate", - "IngestionEvent_SdkLog", - "IngestionEvent_SpanCreate", - "IngestionEvent_SpanUpdate", - "IngestionEvent_TraceCreate", - "IngestionResponse", - "IngestionSuccess", - "IngestionUsage", - "LlmAdapter", - "LlmConnection", - "MapValue", - "MediaContentType", - "MembershipDeletionResponse", - "MembershipRequest", - "MembershipResponse", - "MembershipRole", - "MembershipsResponse", - "MethodNotAllowedError", - "MetricsResponse", - "MetricsV2Response", - "Model", - "ModelPrice", - "ModelUsageUnit", - "NotFoundError", - "NumericScore", - "NumericScoreV1", - "Observation", - "ObservationBody", - "ObservationLevel", - "ObservationType", - "Observations", - "ObservationsV2Meta", - "ObservationsV2Response", - "ObservationsView", - "ObservationsViews", - "OpenAiCompletionUsageSchema", - "OpenAiResponseUsageSchema", - "OpenAiUsage", - "OptionalObservationBody", - "Organization", - "OrganizationApiKey", - "OrganizationApiKeysResponse", - "OrganizationProject", - "OrganizationProjectsResponse", - "OtelAttribute", - "OtelAttributeValue", - "OtelResource", - "OtelResourceSpan", - "OtelScope", - "OtelScopeSpan", - "OtelSpan", - "OtelTraceResponse", - "PaginatedAnnotationQueueItems", - "PaginatedAnnotationQueues", - "PaginatedDatasetItems", - "PaginatedDatasetRunItems", - "PaginatedDatasetRuns", - "PaginatedDatasets", - "PaginatedLlmConnections", - "PaginatedModels", - "PaginatedSessions", - "PatchMediaBody", - "PlaceholderMessage", - "PricingTier", - "PricingTierCondition", - "PricingTierInput", - "PricingTierOperator", - "Project", - "ProjectDeletionResponse", - "Projects", - "Prompt", - "PromptMeta", - "PromptMetaListResponse", - "PromptType", - "Prompt_Chat", - "Prompt_Text", - "ResourceMeta", - "ResourceType", - "ResourceTypesResponse", - "SchemaExtension", - "SchemaResource", - "SchemasResponse", - "ScimEmail", - "ScimFeatureSupport", - "ScimName", - "ScimUser", - "ScimUsersListResponse", - "Score", - "ScoreBody", - "ScoreConfig", - "ScoreConfigDataType", - "ScoreConfigs", - "ScoreDataType", - "ScoreEvent", - "ScoreSource", - "ScoreV1", - "ScoreV1_Boolean", - "ScoreV1_Categorical", - "ScoreV1_Numeric", - "Score_Boolean", - "Score_Categorical", - "Score_Correction", - "Score_Numeric", - "SdkLogBody", - "SdkLogEvent", - "ServiceProviderConfig", - "ServiceUnavailableError", - "Session", - "SessionWithTraces", - "Sort", - "TextPrompt", - "Trace", - "TraceBody", - "TraceEvent", - "TraceWithDetails", - "TraceWithFullDetails", - "Traces", - "UnauthorizedError", - "UpdateAnnotationQueueItemRequest", - "UpdateEventBody", - "UpdateGenerationBody", - "UpdateGenerationEvent", - "UpdateObservationEvent", - "UpdateScoreConfigRequest", - "UpdateSpanBody", - "UpdateSpanEvent", - "UpsertLlmConnectionRequest", - "Usage", - "UsageDetails", - "UserMeta", - "annotation_queues", - "blob_storage_integrations", - "comments", - "commons", - "dataset_items", - "dataset_run_items", - "datasets", - "health", - "ingestion", - "llm_connections", - "media", - "metrics", - "metrics_v_2", - "models", - "observations", - "observations_v_2", - "opentelemetry", - "organizations", - "projects", - "prompt_version", - "prompts", - "scim", - "score", - "score_configs", - "score_v_2", - "sessions", - "trace", - "utils", -] diff --git a/langfuse/api/resources/annotation_queues/__init__.py b/langfuse/api/resources/annotation_queues/__init__.py deleted file mode 100644 index eed891727..000000000 --- a/langfuse/api/resources/annotation_queues/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - AnnotationQueue, - AnnotationQueueAssignmentRequest, - AnnotationQueueItem, - AnnotationQueueObjectType, - AnnotationQueueStatus, - CreateAnnotationQueueAssignmentResponse, - CreateAnnotationQueueItemRequest, - CreateAnnotationQueueRequest, - DeleteAnnotationQueueAssignmentResponse, - DeleteAnnotationQueueItemResponse, - PaginatedAnnotationQueueItems, - PaginatedAnnotationQueues, - UpdateAnnotationQueueItemRequest, -) - -__all__ = [ - "AnnotationQueue", - "AnnotationQueueAssignmentRequest", - "AnnotationQueueItem", - "AnnotationQueueObjectType", - "AnnotationQueueStatus", - "CreateAnnotationQueueAssignmentResponse", - "CreateAnnotationQueueItemRequest", - "CreateAnnotationQueueRequest", - "DeleteAnnotationQueueAssignmentResponse", - "DeleteAnnotationQueueItemResponse", - "PaginatedAnnotationQueueItems", - "PaginatedAnnotationQueues", - "UpdateAnnotationQueueItemRequest", -] diff --git a/langfuse/api/resources/annotation_queues/client.py b/langfuse/api/resources/annotation_queues/client.py deleted file mode 100644 index 97c7c2216..000000000 --- a/langfuse/api/resources/annotation_queues/client.py +++ /dev/null @@ -1,1642 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.annotation_queue import AnnotationQueue -from .types.annotation_queue_assignment_request import AnnotationQueueAssignmentRequest -from .types.annotation_queue_item import AnnotationQueueItem -from .types.annotation_queue_status import AnnotationQueueStatus -from .types.create_annotation_queue_assignment_response import ( - CreateAnnotationQueueAssignmentResponse, -) -from .types.create_annotation_queue_item_request import CreateAnnotationQueueItemRequest -from .types.create_annotation_queue_request import CreateAnnotationQueueRequest -from .types.delete_annotation_queue_assignment_response import ( - DeleteAnnotationQueueAssignmentResponse, -) -from .types.delete_annotation_queue_item_response import ( - DeleteAnnotationQueueItemResponse, -) -from .types.paginated_annotation_queue_items import PaginatedAnnotationQueueItems -from .types.paginated_annotation_queues import PaginatedAnnotationQueues -from .types.update_annotation_queue_item_request import UpdateAnnotationQueueItemRequest - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class AnnotationQueuesClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def list_queues( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedAnnotationQueues: - """ - Get all annotation queues - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedAnnotationQueues - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.list_queues() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/annotation-queues", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedAnnotationQueues, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create_queue( - self, - *, - request: CreateAnnotationQueueRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueue: - """ - Create an annotation queue - - Parameters - ---------- - request : CreateAnnotationQueueRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueue - - Examples - -------- - from langfuse import CreateAnnotationQueueRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.create_queue( - request=CreateAnnotationQueueRequest( - name="name", - score_config_ids=["scoreConfigIds", "scoreConfigIds"], - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/annotation-queues", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueue, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_queue( - self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AnnotationQueue: - """ - Get an annotation queue by ID - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueue - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.get_queue( - queue_id="queueId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueue, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def list_queue_items( - self, - queue_id: str, - *, - status: typing.Optional[AnnotationQueueStatus] = None, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedAnnotationQueueItems: - """ - Get items for a specific annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - status : typing.Optional[AnnotationQueueStatus] - Filter by status - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedAnnotationQueueItems - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.list_queue_items( - queue_id="queueId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", - method="GET", - params={"status": status, "page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedAnnotationQueueItems, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_queue_item( - self, - queue_id: str, - item_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueueItem: - """ - Get a specific item from an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - item_id : str - The unique identifier of the annotation queue item - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueueItem - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.get_queue_item( - queue_id="queueId", - item_id="itemId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueueItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create_queue_item( - self, - queue_id: str, - *, - request: CreateAnnotationQueueItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueueItem: - """ - Add an item to an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request : CreateAnnotationQueueItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueueItem - - Examples - -------- - from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.create_queue_item( - queue_id="queueId", - request=CreateAnnotationQueueItemRequest( - object_id="objectId", - object_type=AnnotationQueueObjectType.TRACE, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueueItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def update_queue_item( - self, - queue_id: str, - item_id: str, - *, - request: UpdateAnnotationQueueItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueueItem: - """ - Update an annotation queue item - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - item_id : str - The unique identifier of the annotation queue item - - request : UpdateAnnotationQueueItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueueItem - - Examples - -------- - from langfuse import UpdateAnnotationQueueItemRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.update_queue_item( - queue_id="queueId", - item_id="itemId", - request=UpdateAnnotationQueueItemRequest(), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", - method="PATCH", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueueItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_queue_item( - self, - queue_id: str, - item_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> DeleteAnnotationQueueItemResponse: - """ - Remove an item from an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - item_id : str - The unique identifier of the annotation queue item - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteAnnotationQueueItemResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.delete_queue_item( - queue_id="queueId", - item_id="itemId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteAnnotationQueueItemResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create_queue_assignment( - self, - queue_id: str, - *, - request: AnnotationQueueAssignmentRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> CreateAnnotationQueueAssignmentResponse: - """ - Create an assignment for a user to an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request : AnnotationQueueAssignmentRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CreateAnnotationQueueAssignmentResponse - - Examples - -------- - from langfuse import AnnotationQueueAssignmentRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.create_queue_assignment( - queue_id="queueId", - request=AnnotationQueueAssignmentRequest( - user_id="userId", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - CreateAnnotationQueueAssignmentResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_queue_assignment( - self, - queue_id: str, - *, - request: AnnotationQueueAssignmentRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> DeleteAnnotationQueueAssignmentResponse: - """ - Delete an assignment for a user to an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request : AnnotationQueueAssignmentRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteAnnotationQueueAssignmentResponse - - Examples - -------- - from langfuse import AnnotationQueueAssignmentRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.annotation_queues.delete_queue_assignment( - queue_id="queueId", - request=AnnotationQueueAssignmentRequest( - user_id="userId", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", - method="DELETE", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteAnnotationQueueAssignmentResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncAnnotationQueuesClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def list_queues( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedAnnotationQueues: - """ - Get all annotation queues - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedAnnotationQueues - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.list_queues() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/annotation-queues", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedAnnotationQueues, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create_queue( - self, - *, - request: CreateAnnotationQueueRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueue: - """ - Create an annotation queue - - Parameters - ---------- - request : CreateAnnotationQueueRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueue - - Examples - -------- - import asyncio - - from langfuse import CreateAnnotationQueueRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.create_queue( - request=CreateAnnotationQueueRequest( - name="name", - score_config_ids=["scoreConfigIds", "scoreConfigIds"], - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/annotation-queues", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueue, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_queue( - self, queue_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AnnotationQueue: - """ - Get an annotation queue by ID - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueue - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.get_queue( - queue_id="queueId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueue, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def list_queue_items( - self, - queue_id: str, - *, - status: typing.Optional[AnnotationQueueStatus] = None, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedAnnotationQueueItems: - """ - Get items for a specific annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - status : typing.Optional[AnnotationQueueStatus] - Filter by status - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedAnnotationQueueItems - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.list_queue_items( - queue_id="queueId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", - method="GET", - params={"status": status, "page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedAnnotationQueueItems, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_queue_item( - self, - queue_id: str, - item_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueueItem: - """ - Get a specific item from an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - item_id : str - The unique identifier of the annotation queue item - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueueItem - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.get_queue_item( - queue_id="queueId", - item_id="itemId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueueItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create_queue_item( - self, - queue_id: str, - *, - request: CreateAnnotationQueueItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueueItem: - """ - Add an item to an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request : CreateAnnotationQueueItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueueItem - - Examples - -------- - import asyncio - - from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.create_queue_item( - queue_id="queueId", - request=CreateAnnotationQueueItemRequest( - object_id="objectId", - object_type=AnnotationQueueObjectType.TRACE, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueueItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def update_queue_item( - self, - queue_id: str, - item_id: str, - *, - request: UpdateAnnotationQueueItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> AnnotationQueueItem: - """ - Update an annotation queue item - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - item_id : str - The unique identifier of the annotation queue item - - request : UpdateAnnotationQueueItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AnnotationQueueItem - - Examples - -------- - import asyncio - - from langfuse import UpdateAnnotationQueueItemRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.update_queue_item( - queue_id="queueId", - item_id="itemId", - request=UpdateAnnotationQueueItemRequest(), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", - method="PATCH", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(AnnotationQueueItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_queue_item( - self, - queue_id: str, - item_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> DeleteAnnotationQueueItemResponse: - """ - Remove an item from an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - item_id : str - The unique identifier of the annotation queue item - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteAnnotationQueueItemResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.delete_queue_item( - queue_id="queueId", - item_id="itemId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/items/{jsonable_encoder(item_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteAnnotationQueueItemResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create_queue_assignment( - self, - queue_id: str, - *, - request: AnnotationQueueAssignmentRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> CreateAnnotationQueueAssignmentResponse: - """ - Create an assignment for a user to an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request : AnnotationQueueAssignmentRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CreateAnnotationQueueAssignmentResponse - - Examples - -------- - import asyncio - - from langfuse import AnnotationQueueAssignmentRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.create_queue_assignment( - queue_id="queueId", - request=AnnotationQueueAssignmentRequest( - user_id="userId", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - CreateAnnotationQueueAssignmentResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_queue_assignment( - self, - queue_id: str, - *, - request: AnnotationQueueAssignmentRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> DeleteAnnotationQueueAssignmentResponse: - """ - Delete an assignment for a user to an annotation queue - - Parameters - ---------- - queue_id : str - The unique identifier of the annotation queue - - request : AnnotationQueueAssignmentRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteAnnotationQueueAssignmentResponse - - Examples - -------- - import asyncio - - from langfuse import AnnotationQueueAssignmentRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.annotation_queues.delete_queue_assignment( - queue_id="queueId", - request=AnnotationQueueAssignmentRequest( - user_id="userId", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/annotation-queues/{jsonable_encoder(queue_id)}/assignments", - method="DELETE", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteAnnotationQueueAssignmentResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/annotation_queues/types/__init__.py b/langfuse/api/resources/annotation_queues/types/__init__.py deleted file mode 100644 index 9f9ce37dd..000000000 --- a/langfuse/api/resources/annotation_queues/types/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .annotation_queue import AnnotationQueue -from .annotation_queue_assignment_request import AnnotationQueueAssignmentRequest -from .annotation_queue_item import AnnotationQueueItem -from .annotation_queue_object_type import AnnotationQueueObjectType -from .annotation_queue_status import AnnotationQueueStatus -from .create_annotation_queue_assignment_response import ( - CreateAnnotationQueueAssignmentResponse, -) -from .create_annotation_queue_item_request import CreateAnnotationQueueItemRequest -from .create_annotation_queue_request import CreateAnnotationQueueRequest -from .delete_annotation_queue_assignment_response import ( - DeleteAnnotationQueueAssignmentResponse, -) -from .delete_annotation_queue_item_response import DeleteAnnotationQueueItemResponse -from .paginated_annotation_queue_items import PaginatedAnnotationQueueItems -from .paginated_annotation_queues import PaginatedAnnotationQueues -from .update_annotation_queue_item_request import UpdateAnnotationQueueItemRequest - -__all__ = [ - "AnnotationQueue", - "AnnotationQueueAssignmentRequest", - "AnnotationQueueItem", - "AnnotationQueueObjectType", - "AnnotationQueueStatus", - "CreateAnnotationQueueAssignmentResponse", - "CreateAnnotationQueueItemRequest", - "CreateAnnotationQueueRequest", - "DeleteAnnotationQueueAssignmentResponse", - "DeleteAnnotationQueueItemResponse", - "PaginatedAnnotationQueueItems", - "PaginatedAnnotationQueues", - "UpdateAnnotationQueueItemRequest", -] diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue.py b/langfuse/api/resources/annotation_queues/types/annotation_queue.py deleted file mode 100644 index c4cc23282..000000000 --- a/langfuse/api/resources/annotation_queues/types/annotation_queue.py +++ /dev/null @@ -1,49 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class AnnotationQueue(pydantic_v1.BaseModel): - id: str - name: str - description: typing.Optional[str] = None - score_config_ids: typing.List[str] = pydantic_v1.Field(alias="scoreConfigIds") - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py b/langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py deleted file mode 100644 index aa3980438..000000000 --- a/langfuse/api/resources/annotation_queues/types/annotation_queue_assignment_request.py +++ /dev/null @@ -1,44 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class AnnotationQueueAssignmentRequest(pydantic_v1.BaseModel): - user_id: str = pydantic_v1.Field(alias="userId") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/annotation_queue_item.py b/langfuse/api/resources/annotation_queues/types/annotation_queue_item.py deleted file mode 100644 index e88829a1f..000000000 --- a/langfuse/api/resources/annotation_queues/types/annotation_queue_item.py +++ /dev/null @@ -1,55 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .annotation_queue_object_type import AnnotationQueueObjectType -from .annotation_queue_status import AnnotationQueueStatus - - -class AnnotationQueueItem(pydantic_v1.BaseModel): - id: str - queue_id: str = pydantic_v1.Field(alias="queueId") - object_id: str = pydantic_v1.Field(alias="objectId") - object_type: AnnotationQueueObjectType = pydantic_v1.Field(alias="objectType") - status: AnnotationQueueStatus - completed_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="completedAt", default=None - ) - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py deleted file mode 100644 index ae6a46862..000000000 --- a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_assignment_response.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateAnnotationQueueAssignmentResponse(pydantic_v1.BaseModel): - user_id: str = pydantic_v1.Field(alias="userId") - queue_id: str = pydantic_v1.Field(alias="queueId") - project_id: str = pydantic_v1.Field(alias="projectId") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_item_request.py b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_item_request.py deleted file mode 100644 index cbf257f29..000000000 --- a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_item_request.py +++ /dev/null @@ -1,51 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .annotation_queue_object_type import AnnotationQueueObjectType -from .annotation_queue_status import AnnotationQueueStatus - - -class CreateAnnotationQueueItemRequest(pydantic_v1.BaseModel): - object_id: str = pydantic_v1.Field(alias="objectId") - object_type: AnnotationQueueObjectType = pydantic_v1.Field(alias="objectType") - status: typing.Optional[AnnotationQueueStatus] = pydantic_v1.Field(default=None) - """ - Defaults to PENDING for new queue items - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py b/langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py deleted file mode 100644 index 7f793cea2..000000000 --- a/langfuse/api/resources/annotation_queues/types/create_annotation_queue_request.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateAnnotationQueueRequest(pydantic_v1.BaseModel): - name: str - description: typing.Optional[str] = None - score_config_ids: typing.List[str] = pydantic_v1.Field(alias="scoreConfigIds") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py b/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py deleted file mode 100644 index e348d546c..000000000 --- a/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_assignment_response.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DeleteAnnotationQueueAssignmentResponse(pydantic_v1.BaseModel): - success: bool - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_item_response.py b/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_item_response.py deleted file mode 100644 index a412c85b7..000000000 --- a/langfuse/api/resources/annotation_queues/types/delete_annotation_queue_item_response.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DeleteAnnotationQueueItemResponse(pydantic_v1.BaseModel): - success: bool - message: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/paginated_annotation_queue_items.py b/langfuse/api/resources/annotation_queues/types/paginated_annotation_queue_items.py deleted file mode 100644 index 587188d89..000000000 --- a/langfuse/api/resources/annotation_queues/types/paginated_annotation_queue_items.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...utils.resources.pagination.types.meta_response import MetaResponse -from .annotation_queue_item import AnnotationQueueItem - - -class PaginatedAnnotationQueueItems(pydantic_v1.BaseModel): - data: typing.List[AnnotationQueueItem] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/paginated_annotation_queues.py b/langfuse/api/resources/annotation_queues/types/paginated_annotation_queues.py deleted file mode 100644 index aba338414..000000000 --- a/langfuse/api/resources/annotation_queues/types/paginated_annotation_queues.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...utils.resources.pagination.types.meta_response import MetaResponse -from .annotation_queue import AnnotationQueue - - -class PaginatedAnnotationQueues(pydantic_v1.BaseModel): - data: typing.List[AnnotationQueue] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/annotation_queues/types/update_annotation_queue_item_request.py b/langfuse/api/resources/annotation_queues/types/update_annotation_queue_item_request.py deleted file mode 100644 index 3b1c130fe..000000000 --- a/langfuse/api/resources/annotation_queues/types/update_annotation_queue_item_request.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .annotation_queue_status import AnnotationQueueStatus - - -class UpdateAnnotationQueueItemRequest(pydantic_v1.BaseModel): - status: typing.Optional[AnnotationQueueStatus] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/__init__.py b/langfuse/api/resources/blob_storage_integrations/__init__.py deleted file mode 100644 index a635fba57..000000000 --- a/langfuse/api/resources/blob_storage_integrations/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - BlobStorageExportFrequency, - BlobStorageExportMode, - BlobStorageIntegrationDeletionResponse, - BlobStorageIntegrationFileType, - BlobStorageIntegrationResponse, - BlobStorageIntegrationType, - BlobStorageIntegrationsResponse, - CreateBlobStorageIntegrationRequest, -) - -__all__ = [ - "BlobStorageExportFrequency", - "BlobStorageExportMode", - "BlobStorageIntegrationDeletionResponse", - "BlobStorageIntegrationFileType", - "BlobStorageIntegrationResponse", - "BlobStorageIntegrationType", - "BlobStorageIntegrationsResponse", - "CreateBlobStorageIntegrationRequest", -] diff --git a/langfuse/api/resources/blob_storage_integrations/client.py b/langfuse/api/resources/blob_storage_integrations/client.py deleted file mode 100644 index 73aec4fa4..000000000 --- a/langfuse/api/resources/blob_storage_integrations/client.py +++ /dev/null @@ -1,492 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.blob_storage_integration_deletion_response import ( - BlobStorageIntegrationDeletionResponse, -) -from .types.blob_storage_integration_response import BlobStorageIntegrationResponse -from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse -from .types.create_blob_storage_integration_request import ( - CreateBlobStorageIntegrationRequest, -) - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class BlobStorageIntegrationsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get_blob_storage_integrations( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> BlobStorageIntegrationsResponse: - """ - Get all blob storage integrations for the organization (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - BlobStorageIntegrationsResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.blob_storage_integrations.get_blob_storage_integrations() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/integrations/blob-storage", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - BlobStorageIntegrationsResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def upsert_blob_storage_integration( - self, - *, - request: CreateBlobStorageIntegrationRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> BlobStorageIntegrationResponse: - """ - Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. - - Parameters - ---------- - request : CreateBlobStorageIntegrationRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - BlobStorageIntegrationResponse - - Examples - -------- - from langfuse import ( - BlobStorageExportFrequency, - BlobStorageExportMode, - BlobStorageIntegrationFileType, - BlobStorageIntegrationType, - CreateBlobStorageIntegrationRequest, - ) - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.blob_storage_integrations.upsert_blob_storage_integration( - request=CreateBlobStorageIntegrationRequest( - project_id="projectId", - type=BlobStorageIntegrationType.S_3, - bucket_name="bucketName", - region="region", - export_frequency=BlobStorageExportFrequency.HOURLY, - enabled=True, - force_path_style=True, - file_type=BlobStorageIntegrationFileType.JSON, - export_mode=BlobStorageExportMode.FULL_HISTORY, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/integrations/blob-storage", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - BlobStorageIntegrationResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_blob_storage_integration( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> BlobStorageIntegrationDeletionResponse: - """ - Delete a blob storage integration by ID (requires organization-scoped API key) - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - BlobStorageIntegrationDeletionResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.blob_storage_integrations.delete_blob_storage_integration( - id="id", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - BlobStorageIntegrationDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncBlobStorageIntegrationsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get_blob_storage_integrations( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> BlobStorageIntegrationsResponse: - """ - Get all blob storage integrations for the organization (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - BlobStorageIntegrationsResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.blob_storage_integrations.get_blob_storage_integrations() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/integrations/blob-storage", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - BlobStorageIntegrationsResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def upsert_blob_storage_integration( - self, - *, - request: CreateBlobStorageIntegrationRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> BlobStorageIntegrationResponse: - """ - Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket. - - Parameters - ---------- - request : CreateBlobStorageIntegrationRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - BlobStorageIntegrationResponse - - Examples - -------- - import asyncio - - from langfuse import ( - BlobStorageExportFrequency, - BlobStorageExportMode, - BlobStorageIntegrationFileType, - BlobStorageIntegrationType, - CreateBlobStorageIntegrationRequest, - ) - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.blob_storage_integrations.upsert_blob_storage_integration( - request=CreateBlobStorageIntegrationRequest( - project_id="projectId", - type=BlobStorageIntegrationType.S_3, - bucket_name="bucketName", - region="region", - export_frequency=BlobStorageExportFrequency.HOURLY, - enabled=True, - force_path_style=True, - file_type=BlobStorageIntegrationFileType.JSON, - export_mode=BlobStorageExportMode.FULL_HISTORY, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/integrations/blob-storage", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - BlobStorageIntegrationResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_blob_storage_integration( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> BlobStorageIntegrationDeletionResponse: - """ - Delete a blob storage integration by ID (requires organization-scoped API key) - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - BlobStorageIntegrationDeletionResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.blob_storage_integrations.delete_blob_storage_integration( - id="id", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - BlobStorageIntegrationDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/blob_storage_integrations/types/__init__.py b/langfuse/api/resources/blob_storage_integrations/types/__init__.py deleted file mode 100644 index 621196c11..000000000 --- a/langfuse/api/resources/blob_storage_integrations/types/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .blob_storage_export_frequency import BlobStorageExportFrequency -from .blob_storage_export_mode import BlobStorageExportMode -from .blob_storage_integration_deletion_response import ( - BlobStorageIntegrationDeletionResponse, -) -from .blob_storage_integration_file_type import BlobStorageIntegrationFileType -from .blob_storage_integration_response import BlobStorageIntegrationResponse -from .blob_storage_integration_type import BlobStorageIntegrationType -from .blob_storage_integrations_response import BlobStorageIntegrationsResponse -from .create_blob_storage_integration_request import CreateBlobStorageIntegrationRequest - -__all__ = [ - "BlobStorageExportFrequency", - "BlobStorageExportMode", - "BlobStorageIntegrationDeletionResponse", - "BlobStorageIntegrationFileType", - "BlobStorageIntegrationResponse", - "BlobStorageIntegrationType", - "BlobStorageIntegrationsResponse", - "CreateBlobStorageIntegrationRequest", -] diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py deleted file mode 100644 index 4305cff2f..000000000 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_deletion_response.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class BlobStorageIntegrationDeletionResponse(pydantic_v1.BaseModel): - message: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py deleted file mode 100644 index e308e8113..000000000 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_response.py +++ /dev/null @@ -1,75 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .blob_storage_export_frequency import BlobStorageExportFrequency -from .blob_storage_export_mode import BlobStorageExportMode -from .blob_storage_integration_file_type import BlobStorageIntegrationFileType -from .blob_storage_integration_type import BlobStorageIntegrationType - - -class BlobStorageIntegrationResponse(pydantic_v1.BaseModel): - id: str - project_id: str = pydantic_v1.Field(alias="projectId") - type: BlobStorageIntegrationType - bucket_name: str = pydantic_v1.Field(alias="bucketName") - endpoint: typing.Optional[str] = None - region: str - access_key_id: typing.Optional[str] = pydantic_v1.Field( - alias="accessKeyId", default=None - ) - prefix: str - export_frequency: BlobStorageExportFrequency = pydantic_v1.Field( - alias="exportFrequency" - ) - enabled: bool - force_path_style: bool = pydantic_v1.Field(alias="forcePathStyle") - file_type: BlobStorageIntegrationFileType = pydantic_v1.Field(alias="fileType") - export_mode: BlobStorageExportMode = pydantic_v1.Field(alias="exportMode") - export_start_date: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="exportStartDate", default=None - ) - next_sync_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="nextSyncAt", default=None - ) - last_sync_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="lastSyncAt", default=None - ) - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py deleted file mode 100644 index 38bacbf85..000000000 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integration_type.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import enum -import typing - -T_Result = typing.TypeVar("T_Result") - - -class BlobStorageIntegrationType(str, enum.Enum): - S_3 = "S3" - S_3_COMPATIBLE = "S3_COMPATIBLE" - AZURE_BLOB_STORAGE = "AZURE_BLOB_STORAGE" - - def visit( - self, - s_3: typing.Callable[[], T_Result], - s_3_compatible: typing.Callable[[], T_Result], - azure_blob_storage: typing.Callable[[], T_Result], - ) -> T_Result: - if self is BlobStorageIntegrationType.S_3: - return s_3() - if self is BlobStorageIntegrationType.S_3_COMPATIBLE: - return s_3_compatible() - if self is BlobStorageIntegrationType.AZURE_BLOB_STORAGE: - return azure_blob_storage() diff --git a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py b/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py deleted file mode 100644 index c6231a23e..000000000 --- a/langfuse/api/resources/blob_storage_integrations/types/blob_storage_integrations_response.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .blob_storage_integration_response import BlobStorageIntegrationResponse - - -class BlobStorageIntegrationsResponse(pydantic_v1.BaseModel): - data: typing.List[BlobStorageIntegrationResponse] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py deleted file mode 100644 index 31b5779c6..000000000 --- a/langfuse/api/resources/blob_storage_integrations/types/create_blob_storage_integration_request.py +++ /dev/null @@ -1,108 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .blob_storage_export_frequency import BlobStorageExportFrequency -from .blob_storage_export_mode import BlobStorageExportMode -from .blob_storage_integration_file_type import BlobStorageIntegrationFileType -from .blob_storage_integration_type import BlobStorageIntegrationType - - -class CreateBlobStorageIntegrationRequest(pydantic_v1.BaseModel): - project_id: str = pydantic_v1.Field(alias="projectId") - """ - ID of the project in which to configure the blob storage integration - """ - - type: BlobStorageIntegrationType - bucket_name: str = pydantic_v1.Field(alias="bucketName") - """ - Name of the storage bucket - """ - - endpoint: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Custom endpoint URL (required for S3_COMPATIBLE type) - """ - - region: str = pydantic_v1.Field() - """ - Storage region - """ - - access_key_id: typing.Optional[str] = pydantic_v1.Field( - alias="accessKeyId", default=None - ) - """ - Access key ID for authentication - """ - - secret_access_key: typing.Optional[str] = pydantic_v1.Field( - alias="secretAccessKey", default=None - ) - """ - Secret access key for authentication (will be encrypted when stored) - """ - - prefix: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Path prefix for exported files (must end with forward slash if provided) - """ - - export_frequency: BlobStorageExportFrequency = pydantic_v1.Field( - alias="exportFrequency" - ) - enabled: bool = pydantic_v1.Field() - """ - Whether the integration is active - """ - - force_path_style: bool = pydantic_v1.Field(alias="forcePathStyle") - """ - Use path-style URLs for S3 requests - """ - - file_type: BlobStorageIntegrationFileType = pydantic_v1.Field(alias="fileType") - export_mode: BlobStorageExportMode = pydantic_v1.Field(alias="exportMode") - export_start_date: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="exportStartDate", default=None - ) - """ - Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/comments/__init__.py b/langfuse/api/resources/comments/__init__.py deleted file mode 100644 index e40c8546f..000000000 --- a/langfuse/api/resources/comments/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import CreateCommentRequest, CreateCommentResponse, GetCommentsResponse - -__all__ = ["CreateCommentRequest", "CreateCommentResponse", "GetCommentsResponse"] diff --git a/langfuse/api/resources/comments/client.py b/langfuse/api/resources/comments/client.py deleted file mode 100644 index 9c78ca23f..000000000 --- a/langfuse/api/resources/comments/client.py +++ /dev/null @@ -1,520 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.comment import Comment -from .types.create_comment_request import CreateCommentRequest -from .types.create_comment_response import CreateCommentResponse -from .types.get_comments_response import GetCommentsResponse - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class CommentsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def create( - self, - *, - request: CreateCommentRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> CreateCommentResponse: - """ - Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). - - Parameters - ---------- - request : CreateCommentRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CreateCommentResponse - - Examples - -------- - from langfuse import CreateCommentRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.comments.create( - request=CreateCommentRequest( - project_id="projectId", - object_type="objectType", - object_id="objectId", - content="content", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/comments", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(CreateCommentResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - object_type: typing.Optional[str] = None, - object_id: typing.Optional[str] = None, - author_user_id: typing.Optional[str] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> GetCommentsResponse: - """ - Get all comments - - Parameters - ---------- - page : typing.Optional[int] - Page number, starts at 1. - - limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit - - object_type : typing.Optional[str] - Filter comments by object type (trace, observation, session, prompt). - - object_id : typing.Optional[str] - Filter comments by object id. If objectType is not provided, an error will be thrown. - - author_user_id : typing.Optional[str] - Filter comments by author user id. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - GetCommentsResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.comments.get() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/comments", - method="GET", - params={ - "page": page, - "limit": limit, - "objectType": object_type, - "objectId": object_id, - "authorUserId": author_user_id, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(GetCommentsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_by_id( - self, - comment_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> Comment: - """ - Get a comment by id - - Parameters - ---------- - comment_id : str - The unique langfuse identifier of a comment - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Comment - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.comments.get_by_id( - comment_id="commentId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/comments/{jsonable_encoder(comment_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Comment, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncCommentsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def create( - self, - *, - request: CreateCommentRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> CreateCommentResponse: - """ - Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). - - Parameters - ---------- - request : CreateCommentRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CreateCommentResponse - - Examples - -------- - import asyncio - - from langfuse import CreateCommentRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.comments.create( - request=CreateCommentRequest( - project_id="projectId", - object_type="objectType", - object_id="objectId", - content="content", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/comments", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(CreateCommentResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - object_type: typing.Optional[str] = None, - object_id: typing.Optional[str] = None, - author_user_id: typing.Optional[str] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> GetCommentsResponse: - """ - Get all comments - - Parameters - ---------- - page : typing.Optional[int] - Page number, starts at 1. - - limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit - - object_type : typing.Optional[str] - Filter comments by object type (trace, observation, session, prompt). - - object_id : typing.Optional[str] - Filter comments by object id. If objectType is not provided, an error will be thrown. - - author_user_id : typing.Optional[str] - Filter comments by author user id. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - GetCommentsResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.comments.get() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/comments", - method="GET", - params={ - "page": page, - "limit": limit, - "objectType": object_type, - "objectId": object_id, - "authorUserId": author_user_id, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(GetCommentsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_by_id( - self, - comment_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> Comment: - """ - Get a comment by id - - Parameters - ---------- - comment_id : str - The unique langfuse identifier of a comment - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Comment - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.comments.get_by_id( - comment_id="commentId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/comments/{jsonable_encoder(comment_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Comment, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/comments/types/__init__.py b/langfuse/api/resources/comments/types/__init__.py deleted file mode 100644 index 13dc1d8d9..000000000 --- a/langfuse/api/resources/comments/types/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_comment_request import CreateCommentRequest -from .create_comment_response import CreateCommentResponse -from .get_comments_response import GetCommentsResponse - -__all__ = ["CreateCommentRequest", "CreateCommentResponse", "GetCommentsResponse"] diff --git a/langfuse/api/resources/comments/types/create_comment_request.py b/langfuse/api/resources/comments/types/create_comment_request.py deleted file mode 100644 index 3c35c64e2..000000000 --- a/langfuse/api/resources/comments/types/create_comment_request.py +++ /dev/null @@ -1,69 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateCommentRequest(pydantic_v1.BaseModel): - project_id: str = pydantic_v1.Field(alias="projectId") - """ - The id of the project to attach the comment to. - """ - - object_type: str = pydantic_v1.Field(alias="objectType") - """ - The type of the object to attach the comment to (trace, observation, session, prompt). - """ - - object_id: str = pydantic_v1.Field(alias="objectId") - """ - The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. - """ - - content: str = pydantic_v1.Field() - """ - The content of the comment. May include markdown. Currently limited to 5000 characters. - """ - - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - """ - The id of the user who created the comment. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/comments/types/create_comment_response.py b/langfuse/api/resources/comments/types/create_comment_response.py deleted file mode 100644 index d7708f798..000000000 --- a/langfuse/api/resources/comments/types/create_comment_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateCommentResponse(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - The id of the created object in Langfuse - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/comments/types/get_comments_response.py b/langfuse/api/resources/comments/types/get_comments_response.py deleted file mode 100644 index 66a8b9527..000000000 --- a/langfuse/api/resources/comments/types/get_comments_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.comment import Comment -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class GetCommentsResponse(pydantic_v1.BaseModel): - data: typing.List[Comment] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/__init__.py b/langfuse/api/resources/commons/__init__.py deleted file mode 100644 index 7105b22c5..000000000 --- a/langfuse/api/resources/commons/__init__.py +++ /dev/null @@ -1,117 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - BaseScore, - BaseScoreV1, - BooleanScore, - BooleanScoreV1, - CategoricalScore, - CategoricalScoreV1, - Comment, - CommentObjectType, - ConfigCategory, - CorrectionScore, - CreateScoreValue, - Dataset, - DatasetItem, - DatasetRun, - DatasetRunItem, - DatasetRunWithItems, - DatasetStatus, - MapValue, - Model, - ModelPrice, - ModelUsageUnit, - NumericScore, - NumericScoreV1, - Observation, - ObservationLevel, - ObservationsView, - PricingTier, - PricingTierCondition, - PricingTierInput, - PricingTierOperator, - Score, - ScoreConfig, - ScoreConfigDataType, - ScoreDataType, - ScoreSource, - ScoreV1, - ScoreV1_Boolean, - ScoreV1_Categorical, - ScoreV1_Numeric, - Score_Boolean, - Score_Categorical, - Score_Correction, - Score_Numeric, - Session, - SessionWithTraces, - Trace, - TraceWithDetails, - TraceWithFullDetails, - Usage, -) -from .errors import ( - AccessDeniedError, - Error, - MethodNotAllowedError, - NotFoundError, - UnauthorizedError, -) - -__all__ = [ - "AccessDeniedError", - "BaseScore", - "BaseScoreV1", - "BooleanScore", - "BooleanScoreV1", - "CategoricalScore", - "CategoricalScoreV1", - "Comment", - "CommentObjectType", - "ConfigCategory", - "CorrectionScore", - "CreateScoreValue", - "Dataset", - "DatasetItem", - "DatasetRun", - "DatasetRunItem", - "DatasetRunWithItems", - "DatasetStatus", - "Error", - "MapValue", - "MethodNotAllowedError", - "Model", - "ModelPrice", - "ModelUsageUnit", - "NotFoundError", - "NumericScore", - "NumericScoreV1", - "Observation", - "ObservationLevel", - "ObservationsView", - "PricingTier", - "PricingTierCondition", - "PricingTierInput", - "PricingTierOperator", - "Score", - "ScoreConfig", - "ScoreConfigDataType", - "ScoreDataType", - "ScoreSource", - "ScoreV1", - "ScoreV1_Boolean", - "ScoreV1_Categorical", - "ScoreV1_Numeric", - "Score_Boolean", - "Score_Categorical", - "Score_Correction", - "Score_Numeric", - "Session", - "SessionWithTraces", - "Trace", - "TraceWithDetails", - "TraceWithFullDetails", - "UnauthorizedError", - "Usage", -] diff --git a/langfuse/api/resources/commons/errors/__init__.py b/langfuse/api/resources/commons/errors/__init__.py deleted file mode 100644 index 0aef2f92f..000000000 --- a/langfuse/api/resources/commons/errors/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .access_denied_error import AccessDeniedError -from .error import Error -from .method_not_allowed_error import MethodNotAllowedError -from .not_found_error import NotFoundError -from .unauthorized_error import UnauthorizedError - -__all__ = [ - "AccessDeniedError", - "Error", - "MethodNotAllowedError", - "NotFoundError", - "UnauthorizedError", -] diff --git a/langfuse/api/resources/commons/errors/access_denied_error.py b/langfuse/api/resources/commons/errors/access_denied_error.py deleted file mode 100644 index 9114ba9ac..000000000 --- a/langfuse/api/resources/commons/errors/access_denied_error.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ....core.api_error import ApiError - - -class AccessDeniedError(ApiError): - def __init__(self, body: typing.Any): - super().__init__(status_code=403, body=body) diff --git a/langfuse/api/resources/commons/errors/error.py b/langfuse/api/resources/commons/errors/error.py deleted file mode 100644 index 06020120c..000000000 --- a/langfuse/api/resources/commons/errors/error.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ....core.api_error import ApiError - - -class Error(ApiError): - def __init__(self, body: typing.Any): - super().__init__(status_code=400, body=body) diff --git a/langfuse/api/resources/commons/errors/method_not_allowed_error.py b/langfuse/api/resources/commons/errors/method_not_allowed_error.py deleted file mode 100644 index 32731a5c7..000000000 --- a/langfuse/api/resources/commons/errors/method_not_allowed_error.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ....core.api_error import ApiError - - -class MethodNotAllowedError(ApiError): - def __init__(self, body: typing.Any): - super().__init__(status_code=405, body=body) diff --git a/langfuse/api/resources/commons/errors/not_found_error.py b/langfuse/api/resources/commons/errors/not_found_error.py deleted file mode 100644 index 564ffca2c..000000000 --- a/langfuse/api/resources/commons/errors/not_found_error.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ....core.api_error import ApiError - - -class NotFoundError(ApiError): - def __init__(self, body: typing.Any): - super().__init__(status_code=404, body=body) diff --git a/langfuse/api/resources/commons/errors/unauthorized_error.py b/langfuse/api/resources/commons/errors/unauthorized_error.py deleted file mode 100644 index 2997f54f6..000000000 --- a/langfuse/api/resources/commons/errors/unauthorized_error.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ....core.api_error import ApiError - - -class UnauthorizedError(ApiError): - def __init__(self, body: typing.Any): - super().__init__(status_code=401, body=body) diff --git a/langfuse/api/resources/commons/types/__init__.py b/langfuse/api/resources/commons/types/__init__.py deleted file mode 100644 index df87680b7..000000000 --- a/langfuse/api/resources/commons/types/__init__.py +++ /dev/null @@ -1,102 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .base_score import BaseScore -from .base_score_v_1 import BaseScoreV1 -from .boolean_score import BooleanScore -from .boolean_score_v_1 import BooleanScoreV1 -from .categorical_score import CategoricalScore -from .categorical_score_v_1 import CategoricalScoreV1 -from .comment import Comment -from .comment_object_type import CommentObjectType -from .config_category import ConfigCategory -from .correction_score import CorrectionScore -from .create_score_value import CreateScoreValue -from .dataset import Dataset -from .dataset_item import DatasetItem -from .dataset_run import DatasetRun -from .dataset_run_item import DatasetRunItem -from .dataset_run_with_items import DatasetRunWithItems -from .dataset_status import DatasetStatus -from .map_value import MapValue -from .model import Model -from .model_price import ModelPrice -from .model_usage_unit import ModelUsageUnit -from .numeric_score import NumericScore -from .numeric_score_v_1 import NumericScoreV1 -from .observation import Observation -from .observation_level import ObservationLevel -from .observations_view import ObservationsView -from .pricing_tier import PricingTier -from .pricing_tier_condition import PricingTierCondition -from .pricing_tier_input import PricingTierInput -from .pricing_tier_operator import PricingTierOperator -from .score import ( - Score, - Score_Boolean, - Score_Categorical, - Score_Correction, - Score_Numeric, -) -from .score_config import ScoreConfig -from .score_config_data_type import ScoreConfigDataType -from .score_data_type import ScoreDataType -from .score_source import ScoreSource -from .score_v_1 import ScoreV1, ScoreV1_Boolean, ScoreV1_Categorical, ScoreV1_Numeric -from .session import Session -from .session_with_traces import SessionWithTraces -from .trace import Trace -from .trace_with_details import TraceWithDetails -from .trace_with_full_details import TraceWithFullDetails -from .usage import Usage - -__all__ = [ - "BaseScore", - "BaseScoreV1", - "BooleanScore", - "BooleanScoreV1", - "CategoricalScore", - "CategoricalScoreV1", - "Comment", - "CommentObjectType", - "ConfigCategory", - "CorrectionScore", - "CreateScoreValue", - "Dataset", - "DatasetItem", - "DatasetRun", - "DatasetRunItem", - "DatasetRunWithItems", - "DatasetStatus", - "MapValue", - "Model", - "ModelPrice", - "ModelUsageUnit", - "NumericScore", - "NumericScoreV1", - "Observation", - "ObservationLevel", - "ObservationsView", - "PricingTier", - "PricingTierCondition", - "PricingTierInput", - "PricingTierOperator", - "Score", - "ScoreConfig", - "ScoreConfigDataType", - "ScoreDataType", - "ScoreSource", - "ScoreV1", - "ScoreV1_Boolean", - "ScoreV1_Categorical", - "ScoreV1_Numeric", - "Score_Boolean", - "Score_Categorical", - "Score_Correction", - "Score_Numeric", - "Session", - "SessionWithTraces", - "Trace", - "TraceWithDetails", - "TraceWithFullDetails", - "Usage", -] diff --git a/langfuse/api/resources/commons/types/base_score.py b/langfuse/api/resources/commons/types/base_score.py deleted file mode 100644 index c6a0d739a..000000000 --- a/langfuse/api/resources/commons/types/base_score.py +++ /dev/null @@ -1,107 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .score_source import ScoreSource - - -class BaseScore(pydantic_v1.BaseModel): - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - """ - The trace ID associated with the score - """ - - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - """ - The session ID associated with the score - """ - - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - """ - The observation ID associated with the score - """ - - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - """ - The dataset run ID associated with the score - """ - - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - """ - The user ID of the author - """ - - comment: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Comment on the score - """ - - metadata: typing.Any = pydantic_v1.Field() - """ - Metadata associated with the score - """ - - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - """ - Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range - """ - - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - """ - The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. - """ - - environment: str = pydantic_v1.Field() - """ - The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/base_score_v_1.py b/langfuse/api/resources/commons/types/base_score_v_1.py deleted file mode 100644 index 0350864dc..000000000 --- a/langfuse/api/resources/commons/types/base_score_v_1.py +++ /dev/null @@ -1,89 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .score_source import ScoreSource - - -class BaseScoreV1(pydantic_v1.BaseModel): - id: str - trace_id: str = pydantic_v1.Field(alias="traceId") - name: str - source: ScoreSource - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - """ - The observation ID associated with the score - """ - - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - """ - The user ID of the author - """ - - comment: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Comment on the score - """ - - metadata: typing.Any = pydantic_v1.Field() - """ - Metadata associated with the score - """ - - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - """ - Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range - """ - - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - """ - The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. - """ - - environment: str = pydantic_v1.Field() - """ - The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/boolean_score.py b/langfuse/api/resources/commons/types/boolean_score.py deleted file mode 100644 index d838b7db9..000000000 --- a/langfuse/api/resources/commons/types/boolean_score.py +++ /dev/null @@ -1,53 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score import BaseScore - - -class BooleanScore(BaseScore): - value: float = pydantic_v1.Field() - """ - The numeric value of the score. Equals 1 for "True" and 0 for "False" - """ - - string_value: str = pydantic_v1.Field(alias="stringValue") - """ - The string representation of the score value. Is inferred from the numeric value and equals "True" or "False" - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/boolean_score_v_1.py b/langfuse/api/resources/commons/types/boolean_score_v_1.py deleted file mode 100644 index 9f8e8935f..000000000 --- a/langfuse/api/resources/commons/types/boolean_score_v_1.py +++ /dev/null @@ -1,53 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score_v_1 import BaseScoreV1 - - -class BooleanScoreV1(BaseScoreV1): - value: float = pydantic_v1.Field() - """ - The numeric value of the score. Equals 1 for "True" and 0 for "False" - """ - - string_value: str = pydantic_v1.Field(alias="stringValue") - """ - The string representation of the score value. Is inferred from the numeric value and equals "True" or "False" - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/categorical_score.py b/langfuse/api/resources/commons/types/categorical_score.py deleted file mode 100644 index 363ed03ff..000000000 --- a/langfuse/api/resources/commons/types/categorical_score.py +++ /dev/null @@ -1,53 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score import BaseScore - - -class CategoricalScore(BaseScore): - value: float = pydantic_v1.Field() - """ - Represents the numeric category mapping of the stringValue. If no config is linked, defaults to 0. - """ - - string_value: str = pydantic_v1.Field(alias="stringValue") - """ - The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/categorical_score_v_1.py b/langfuse/api/resources/commons/types/categorical_score_v_1.py deleted file mode 100644 index 2aa42d586..000000000 --- a/langfuse/api/resources/commons/types/categorical_score_v_1.py +++ /dev/null @@ -1,53 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score_v_1 import BaseScoreV1 - - -class CategoricalScoreV1(BaseScoreV1): - value: float = pydantic_v1.Field() - """ - Represents the numeric category mapping of the stringValue. If no config is linked, defaults to 0. - """ - - string_value: str = pydantic_v1.Field(alias="stringValue") - """ - The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/comment.py b/langfuse/api/resources/commons/types/comment.py deleted file mode 100644 index bf8506797..000000000 --- a/langfuse/api/resources/commons/types/comment.py +++ /dev/null @@ -1,57 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .comment_object_type import CommentObjectType - - -class Comment(pydantic_v1.BaseModel): - id: str - project_id: str = pydantic_v1.Field(alias="projectId") - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - object_type: CommentObjectType = pydantic_v1.Field(alias="objectType") - object_id: str = pydantic_v1.Field(alias="objectId") - content: str - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - """ - The user ID of the comment author - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/config_category.py b/langfuse/api/resources/commons/types/config_category.py deleted file mode 100644 index b1cbde9f2..000000000 --- a/langfuse/api/resources/commons/types/config_category.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ConfigCategory(pydantic_v1.BaseModel): - value: float - label: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/correction_score.py b/langfuse/api/resources/commons/types/correction_score.py deleted file mode 100644 index 26abeae49..000000000 --- a/langfuse/api/resources/commons/types/correction_score.py +++ /dev/null @@ -1,53 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score import BaseScore - - -class CorrectionScore(BaseScore): - value: float = pydantic_v1.Field() - """ - The numeric value of the score. Always 0 for correction scores. - """ - - string_value: str = pydantic_v1.Field(alias="stringValue") - """ - The string representation of the correction content - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/dataset.py b/langfuse/api/resources/commons/types/dataset.py deleted file mode 100644 index db54a8ee2..000000000 --- a/langfuse/api/resources/commons/types/dataset.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class Dataset(pydantic_v1.BaseModel): - id: str - name: str - description: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Description of the dataset - """ - - metadata: typing.Any = pydantic_v1.Field() - """ - Metadata associated with the dataset - """ - - input_schema: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="inputSchema", default=None - ) - """ - JSON Schema for validating dataset item inputs - """ - - expected_output_schema: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="expectedOutputSchema", default=None - ) - """ - JSON Schema for validating dataset item expected outputs - """ - - project_id: str = pydantic_v1.Field(alias="projectId") - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/dataset_item.py b/langfuse/api/resources/commons/types/dataset_item.py deleted file mode 100644 index eadd57f64..000000000 --- a/langfuse/api/resources/commons/types/dataset_item.py +++ /dev/null @@ -1,79 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .dataset_status import DatasetStatus - - -class DatasetItem(pydantic_v1.BaseModel): - id: str - status: DatasetStatus - input: typing.Any = pydantic_v1.Field() - """ - Input data for the dataset item - """ - - expected_output: typing.Any = pydantic_v1.Field(alias="expectedOutput") - """ - Expected output for the dataset item - """ - - metadata: typing.Any = pydantic_v1.Field() - """ - Metadata associated with the dataset item - """ - - source_trace_id: typing.Optional[str] = pydantic_v1.Field( - alias="sourceTraceId", default=None - ) - """ - The trace ID that sourced this dataset item - """ - - source_observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="sourceObservationId", default=None - ) - """ - The observation ID that sourced this dataset item - """ - - dataset_id: str = pydantic_v1.Field(alias="datasetId") - dataset_name: str = pydantic_v1.Field(alias="datasetName") - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/dataset_run.py b/langfuse/api/resources/commons/types/dataset_run.py deleted file mode 100644 index e130738de..000000000 --- a/langfuse/api/resources/commons/types/dataset_run.py +++ /dev/null @@ -1,82 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DatasetRun(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - Unique identifier of the dataset run - """ - - name: str = pydantic_v1.Field() - """ - Name of the dataset run - """ - - description: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Description of the run - """ - - metadata: typing.Any = pydantic_v1.Field() - """ - Metadata of the dataset run - """ - - dataset_id: str = pydantic_v1.Field(alias="datasetId") - """ - Id of the associated dataset - """ - - dataset_name: str = pydantic_v1.Field(alias="datasetName") - """ - Name of the associated dataset - """ - - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - """ - The date and time when the dataset run was created - """ - - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - """ - The date and time when the dataset run was last updated - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/dataset_run_item.py b/langfuse/api/resources/commons/types/dataset_run_item.py deleted file mode 100644 index ca41ae5c6..000000000 --- a/langfuse/api/resources/commons/types/dataset_run_item.py +++ /dev/null @@ -1,57 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DatasetRunItem(pydantic_v1.BaseModel): - id: str - dataset_run_id: str = pydantic_v1.Field(alias="datasetRunId") - dataset_run_name: str = pydantic_v1.Field(alias="datasetRunName") - dataset_item_id: str = pydantic_v1.Field(alias="datasetItemId") - trace_id: str = pydantic_v1.Field(alias="traceId") - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - """ - The observation ID associated with this run item - """ - - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/dataset_run_with_items.py b/langfuse/api/resources/commons/types/dataset_run_with_items.py deleted file mode 100644 index 647d2c553..000000000 --- a/langfuse/api/resources/commons/types/dataset_run_with_items.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .dataset_run import DatasetRun -from .dataset_run_item import DatasetRunItem - - -class DatasetRunWithItems(DatasetRun): - dataset_run_items: typing.List[DatasetRunItem] = pydantic_v1.Field( - alias="datasetRunItems" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/model_price.py b/langfuse/api/resources/commons/types/model_price.py deleted file mode 100644 index 8882004e7..000000000 --- a/langfuse/api/resources/commons/types/model_price.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ModelPrice(pydantic_v1.BaseModel): - price: float - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/numeric_score.py b/langfuse/api/resources/commons/types/numeric_score.py deleted file mode 100644 index d7f860cd5..000000000 --- a/langfuse/api/resources/commons/types/numeric_score.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score import BaseScore - - -class NumericScore(BaseScore): - value: float = pydantic_v1.Field() - """ - The numeric value of the score - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/numeric_score_v_1.py b/langfuse/api/resources/commons/types/numeric_score_v_1.py deleted file mode 100644 index 773d84b46..000000000 --- a/langfuse/api/resources/commons/types/numeric_score_v_1.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_score_v_1 import BaseScoreV1 - - -class NumericScoreV1(BaseScoreV1): - value: float = pydantic_v1.Field() - """ - The numeric value of the score - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/observation.py b/langfuse/api/resources/commons/types/observation.py deleted file mode 100644 index 1c343aa76..000000000 --- a/langfuse/api/resources/commons/types/observation.py +++ /dev/null @@ -1,157 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .observation_level import ObservationLevel -from .usage import Usage - - -class Observation(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - The unique identifier of the observation - """ - - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - """ - The trace ID associated with the observation - """ - - type: str = pydantic_v1.Field() - """ - The type of the observation - """ - - name: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The name of the observation - """ - - start_time: dt.datetime = pydantic_v1.Field(alias="startTime") - """ - The start time of the observation - """ - - end_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="endTime", default=None - ) - """ - The end time of the observation. - """ - - completion_start_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="completionStartTime", default=None - ) - """ - The completion start time of the observation - """ - - model: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The model used for the observation - """ - - model_parameters: typing.Any = pydantic_v1.Field(alias="modelParameters") - """ - The parameters of the model used for the observation - """ - - input: typing.Any = pydantic_v1.Field() - """ - The input data of the observation - """ - - version: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The version of the observation - """ - - metadata: typing.Any = pydantic_v1.Field() - """ - Additional metadata of the observation - """ - - output: typing.Any = pydantic_v1.Field() - """ - The output data of the observation - """ - - usage: Usage = pydantic_v1.Field() - """ - (Deprecated. Use usageDetails and costDetails instead.) The usage data of the observation - """ - - level: ObservationLevel = pydantic_v1.Field() - """ - The level of the observation - """ - - status_message: typing.Optional[str] = pydantic_v1.Field( - alias="statusMessage", default=None - ) - """ - The status message of the observation - """ - - parent_observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="parentObservationId", default=None - ) - """ - The parent observation ID - """ - - prompt_id: typing.Optional[str] = pydantic_v1.Field(alias="promptId", default=None) - """ - The prompt ID associated with the observation - """ - - usage_details: typing.Dict[str, int] = pydantic_v1.Field(alias="usageDetails") - """ - The usage details of the observation. Key is the name of the usage metric, value is the number of units consumed. The total key is the sum of all (non-total) usage metrics or the total value ingested. - """ - - cost_details: typing.Dict[str, float] = pydantic_v1.Field(alias="costDetails") - """ - The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested. - """ - - environment: str = pydantic_v1.Field() - """ - The environment from which this observation originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/observations_view.py b/langfuse/api/resources/commons/types/observations_view.py deleted file mode 100644 index e011fa32b..000000000 --- a/langfuse/api/resources/commons/types/observations_view.py +++ /dev/null @@ -1,116 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .observation import Observation - - -class ObservationsView(Observation): - prompt_name: typing.Optional[str] = pydantic_v1.Field( - alias="promptName", default=None - ) - """ - The name of the prompt associated with the observation - """ - - prompt_version: typing.Optional[int] = pydantic_v1.Field( - alias="promptVersion", default=None - ) - """ - The version of the prompt associated with the observation - """ - - model_id: typing.Optional[str] = pydantic_v1.Field(alias="modelId", default=None) - """ - The unique identifier of the model - """ - - input_price: typing.Optional[float] = pydantic_v1.Field( - alias="inputPrice", default=None - ) - """ - The price of the input in USD - """ - - output_price: typing.Optional[float] = pydantic_v1.Field( - alias="outputPrice", default=None - ) - """ - The price of the output in USD. - """ - - total_price: typing.Optional[float] = pydantic_v1.Field( - alias="totalPrice", default=None - ) - """ - The total price in USD. - """ - - calculated_input_cost: typing.Optional[float] = pydantic_v1.Field( - alias="calculatedInputCost", default=None - ) - """ - (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the input in USD - """ - - calculated_output_cost: typing.Optional[float] = pydantic_v1.Field( - alias="calculatedOutputCost", default=None - ) - """ - (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the output in USD - """ - - calculated_total_cost: typing.Optional[float] = pydantic_v1.Field( - alias="calculatedTotalCost", default=None - ) - """ - (Deprecated. Use usageDetails and costDetails instead.) The calculated total cost in USD - """ - - latency: typing.Optional[float] = pydantic_v1.Field(default=None) - """ - The latency in seconds. - """ - - time_to_first_token: typing.Optional[float] = pydantic_v1.Field( - alias="timeToFirstToken", default=None - ) - """ - The time to the first token in seconds - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/score.py b/langfuse/api/resources/commons/types/score.py deleted file mode 100644 index dab6eee43..000000000 --- a/langfuse/api/resources/commons/types/score.py +++ /dev/null @@ -1,272 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .score_source import ScoreSource - - -class Score_Numeric(pydantic_v1.BaseModel): - value: float - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field( - alias="dataType", default="NUMERIC" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class Score_Categorical(pydantic_v1.BaseModel): - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field( - alias="dataType", default="CATEGORICAL" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class Score_Boolean(pydantic_v1.BaseModel): - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field( - alias="dataType", default="BOOLEAN" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class Score_Correction(pydantic_v1.BaseModel): - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["CORRECTION"] = pydantic_v1.Field( - alias="dataType", default="CORRECTION" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -Score = typing.Union[Score_Numeric, Score_Categorical, Score_Boolean, Score_Correction] diff --git a/langfuse/api/resources/commons/types/score_config.py b/langfuse/api/resources/commons/types/score_config.py deleted file mode 100644 index 1fda37a09..000000000 --- a/langfuse/api/resources/commons/types/score_config.py +++ /dev/null @@ -1,85 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .config_category import ConfigCategory -from .score_config_data_type import ScoreConfigDataType - - -class ScoreConfig(pydantic_v1.BaseModel): - """ - Configuration for a score - """ - - id: str - name: str - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - project_id: str = pydantic_v1.Field(alias="projectId") - data_type: ScoreConfigDataType = pydantic_v1.Field(alias="dataType") - is_archived: bool = pydantic_v1.Field(alias="isArchived") - """ - Whether the score config is archived. Defaults to false - """ - - min_value: typing.Optional[float] = pydantic_v1.Field( - alias="minValue", default=None - ) - """ - Sets minimum value for numerical scores. If not set, the minimum value defaults to -∞ - """ - - max_value: typing.Optional[float] = pydantic_v1.Field( - alias="maxValue", default=None - ) - """ - Sets maximum value for numerical scores. If not set, the maximum value defaults to +∞ - """ - - categories: typing.Optional[typing.List[ConfigCategory]] = pydantic_v1.Field( - default=None - ) - """ - Configures custom categories for categorical scores - """ - - description: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Description of the score config - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/score_v_1.py b/langfuse/api/resources/commons/types/score_v_1.py deleted file mode 100644 index 74c3f53f9..000000000 --- a/langfuse/api/resources/commons/types/score_v_1.py +++ /dev/null @@ -1,189 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .score_source import ScoreSource - - -class ScoreV1_Numeric(pydantic_v1.BaseModel): - value: float - id: str - trace_id: str = pydantic_v1.Field(alias="traceId") - name: str - source: ScoreSource - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field( - alias="dataType", default="NUMERIC" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class ScoreV1_Categorical(pydantic_v1.BaseModel): - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: str = pydantic_v1.Field(alias="traceId") - name: str - source: ScoreSource - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field( - alias="dataType", default="CATEGORICAL" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class ScoreV1_Boolean(pydantic_v1.BaseModel): - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: str = pydantic_v1.Field(alias="traceId") - name: str - source: ScoreSource - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field( - alias="dataType", default="BOOLEAN" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -ScoreV1 = typing.Union[ScoreV1_Numeric, ScoreV1_Categorical, ScoreV1_Boolean] diff --git a/langfuse/api/resources/commons/types/session.py b/langfuse/api/resources/commons/types/session.py deleted file mode 100644 index ed1557460..000000000 --- a/langfuse/api/resources/commons/types/session.py +++ /dev/null @@ -1,50 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class Session(pydantic_v1.BaseModel): - id: str - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - project_id: str = pydantic_v1.Field(alias="projectId") - environment: str = pydantic_v1.Field() - """ - The environment from which this session originated. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/session_with_traces.py b/langfuse/api/resources/commons/types/session_with_traces.py deleted file mode 100644 index b5465daa9..000000000 --- a/langfuse/api/resources/commons/types/session_with_traces.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .session import Session -from .trace import Trace - - -class SessionWithTraces(Session): - traces: typing.List[Trace] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/trace.py b/langfuse/api/resources/commons/types/trace.py deleted file mode 100644 index 725ad106d..000000000 --- a/langfuse/api/resources/commons/types/trace.py +++ /dev/null @@ -1,109 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class Trace(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - The unique identifier of a trace - """ - - timestamp: dt.datetime = pydantic_v1.Field() - """ - The timestamp when the trace was created - """ - - name: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The name of the trace - """ - - input: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) - """ - The input data of the trace. Can be any JSON. - """ - - output: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) - """ - The output data of the trace. Can be any JSON. - """ - - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - """ - The session identifier associated with the trace - """ - - release: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The release version of the application when the trace was created - """ - - version: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The version of the trace - """ - - user_id: typing.Optional[str] = pydantic_v1.Field(alias="userId", default=None) - """ - The user identifier associated with the trace - """ - - metadata: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) - """ - The metadata associated with the trace. Can be any JSON. - """ - - tags: typing.List[str] = pydantic_v1.Field() - """ - The tags associated with the trace. - """ - - public: bool = pydantic_v1.Field() - """ - Public traces are accessible via url without login - """ - - environment: str = pydantic_v1.Field() - """ - The environment from which this trace originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/trace_with_details.py b/langfuse/api/resources/commons/types/trace_with_details.py deleted file mode 100644 index 795c43adc..000000000 --- a/langfuse/api/resources/commons/types/trace_with_details.py +++ /dev/null @@ -1,70 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .trace import Trace - - -class TraceWithDetails(Trace): - html_path: str = pydantic_v1.Field(alias="htmlPath") - """ - Path of trace in Langfuse UI - """ - - latency: typing.Optional[float] = pydantic_v1.Field(default=None) - """ - Latency of trace in seconds - """ - - total_cost: typing.Optional[float] = pydantic_v1.Field( - alias="totalCost", default=None - ) - """ - Cost of trace in USD - """ - - observations: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - List of observation ids - """ - - scores: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - List of score ids - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/trace_with_full_details.py b/langfuse/api/resources/commons/types/trace_with_full_details.py deleted file mode 100644 index eb2848fa1..000000000 --- a/langfuse/api/resources/commons/types/trace_with_full_details.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .observations_view import ObservationsView -from .score_v_1 import ScoreV1 -from .trace import Trace - - -class TraceWithFullDetails(Trace): - html_path: str = pydantic_v1.Field(alias="htmlPath") - """ - Path of trace in Langfuse UI - """ - - latency: typing.Optional[float] = pydantic_v1.Field(default=None) - """ - Latency of trace in seconds - """ - - total_cost: typing.Optional[float] = pydantic_v1.Field( - alias="totalCost", default=None - ) - """ - Cost of trace in USD - """ - - observations: typing.List[ObservationsView] = pydantic_v1.Field() - """ - List of observations - """ - - scores: typing.List[ScoreV1] = pydantic_v1.Field() - """ - List of scores - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/commons/types/usage.py b/langfuse/api/resources/commons/types/usage.py deleted file mode 100644 index c26620b5b..000000000 --- a/langfuse/api/resources/commons/types/usage.py +++ /dev/null @@ -1,87 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class Usage(pydantic_v1.BaseModel): - """ - (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost - """ - - input: int = pydantic_v1.Field() - """ - Number of input units (e.g. tokens) - """ - - output: int = pydantic_v1.Field() - """ - Number of output units (e.g. tokens) - """ - - total: int = pydantic_v1.Field() - """ - Defaults to input+output if not set - """ - - unit: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Unit of measurement - """ - - input_cost: typing.Optional[float] = pydantic_v1.Field( - alias="inputCost", default=None - ) - """ - USD input cost - """ - - output_cost: typing.Optional[float] = pydantic_v1.Field( - alias="outputCost", default=None - ) - """ - USD output cost - """ - - total_cost: typing.Optional[float] = pydantic_v1.Field( - alias="totalCost", default=None - ) - """ - USD total cost, defaults to input+output - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/dataset_items/__init__.py b/langfuse/api/resources/dataset_items/__init__.py deleted file mode 100644 index 06d2ae527..000000000 --- a/langfuse/api/resources/dataset_items/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - CreateDatasetItemRequest, - DeleteDatasetItemResponse, - PaginatedDatasetItems, -) - -__all__ = [ - "CreateDatasetItemRequest", - "DeleteDatasetItemResponse", - "PaginatedDatasetItems", -] diff --git a/langfuse/api/resources/dataset_items/client.py b/langfuse/api/resources/dataset_items/client.py deleted file mode 100644 index f557c5eab..000000000 --- a/langfuse/api/resources/dataset_items/client.py +++ /dev/null @@ -1,658 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.dataset_item import DatasetItem -from .types.create_dataset_item_request import CreateDatasetItemRequest -from .types.delete_dataset_item_response import DeleteDatasetItemResponse -from .types.paginated_dataset_items import PaginatedDatasetItems - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class DatasetItemsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def create( - self, - *, - request: CreateDatasetItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> DatasetItem: - """ - Create a dataset item - - Parameters - ---------- - request : CreateDatasetItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetItem - - Examples - -------- - from langfuse import CreateDatasetItemRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.dataset_items.create( - request=CreateDatasetItemRequest( - dataset_name="datasetName", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/dataset-items", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> DatasetItem: - """ - Get a dataset item - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetItem - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.dataset_items.get( - id="id", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/dataset-items/{jsonable_encoder(id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def list( - self, - *, - dataset_name: typing.Optional[str] = None, - source_trace_id: typing.Optional[str] = None, - source_observation_id: typing.Optional[str] = None, - version: typing.Optional[dt.datetime] = None, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasetItems: - """ - Get dataset items. Optionally specify a version to get the items as they existed at that point in time. - Note: If version parameter is provided, datasetName must also be provided. - - Parameters - ---------- - dataset_name : typing.Optional[str] - - source_trace_id : typing.Optional[str] - - source_observation_id : typing.Optional[str] - - version : typing.Optional[dt.datetime] - ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). - If provided, returns state of dataset at this timestamp. - If not provided, returns the latest version. Requires datasetName to be specified. - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasetItems - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.dataset_items.list() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/dataset-items", - method="GET", - params={ - "datasetName": dataset_name, - "sourceTraceId": source_trace_id, - "sourceObservationId": source_observation_id, - "version": serialize_datetime(version) if version is not None else None, - "page": page, - "limit": limit, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedDatasetItems, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> DeleteDatasetItemResponse: - """ - Delete a dataset item and all its run items. This action is irreversible. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteDatasetItemResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.dataset_items.delete( - id="id", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/dataset-items/{jsonable_encoder(id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteDatasetItemResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncDatasetItemsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def create( - self, - *, - request: CreateDatasetItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> DatasetItem: - """ - Create a dataset item - - Parameters - ---------- - request : CreateDatasetItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetItem - - Examples - -------- - import asyncio - - from langfuse import CreateDatasetItemRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.dataset_items.create( - request=CreateDatasetItemRequest( - dataset_name="datasetName", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/dataset-items", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> DatasetItem: - """ - Get a dataset item - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetItem - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.dataset_items.get( - id="id", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/dataset-items/{jsonable_encoder(id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def list( - self, - *, - dataset_name: typing.Optional[str] = None, - source_trace_id: typing.Optional[str] = None, - source_observation_id: typing.Optional[str] = None, - version: typing.Optional[dt.datetime] = None, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasetItems: - """ - Get dataset items. Optionally specify a version to get the items as they existed at that point in time. - Note: If version parameter is provided, datasetName must also be provided. - - Parameters - ---------- - dataset_name : typing.Optional[str] - - source_trace_id : typing.Optional[str] - - source_observation_id : typing.Optional[str] - - version : typing.Optional[dt.datetime] - ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). - If provided, returns state of dataset at this timestamp. - If not provided, returns the latest version. Requires datasetName to be specified. - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasetItems - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.dataset_items.list() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/dataset-items", - method="GET", - params={ - "datasetName": dataset_name, - "sourceTraceId": source_trace_id, - "sourceObservationId": source_observation_id, - "version": serialize_datetime(version) if version is not None else None, - "page": page, - "limit": limit, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedDatasetItems, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> DeleteDatasetItemResponse: - """ - Delete a dataset item and all its run items. This action is irreversible. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteDatasetItemResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.dataset_items.delete( - id="id", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/dataset-items/{jsonable_encoder(id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteDatasetItemResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/dataset_items/types/__init__.py b/langfuse/api/resources/dataset_items/types/__init__.py deleted file mode 100644 index 214adce0e..000000000 --- a/langfuse/api/resources/dataset_items/types/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_dataset_item_request import CreateDatasetItemRequest -from .delete_dataset_item_response import DeleteDatasetItemResponse -from .paginated_dataset_items import PaginatedDatasetItems - -__all__ = [ - "CreateDatasetItemRequest", - "DeleteDatasetItemResponse", - "PaginatedDatasetItems", -] diff --git a/langfuse/api/resources/dataset_items/types/create_dataset_item_request.py b/langfuse/api/resources/dataset_items/types/create_dataset_item_request.py deleted file mode 100644 index 111f6819a..000000000 --- a/langfuse/api/resources/dataset_items/types/create_dataset_item_request.py +++ /dev/null @@ -1,65 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.dataset_status import DatasetStatus - - -class CreateDatasetItemRequest(pydantic_v1.BaseModel): - dataset_name: str = pydantic_v1.Field(alias="datasetName") - input: typing.Optional[typing.Any] = None - expected_output: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="expectedOutput", default=None - ) - metadata: typing.Optional[typing.Any] = None - source_trace_id: typing.Optional[str] = pydantic_v1.Field( - alias="sourceTraceId", default=None - ) - source_observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="sourceObservationId", default=None - ) - id: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. - """ - - status: typing.Optional[DatasetStatus] = pydantic_v1.Field(default=None) - """ - Defaults to ACTIVE for newly created items - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/dataset_items/types/delete_dataset_item_response.py b/langfuse/api/resources/dataset_items/types/delete_dataset_item_response.py deleted file mode 100644 index 4d700ff75..000000000 --- a/langfuse/api/resources/dataset_items/types/delete_dataset_item_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DeleteDatasetItemResponse(pydantic_v1.BaseModel): - message: str = pydantic_v1.Field() - """ - Success message after deletion - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/dataset_items/types/paginated_dataset_items.py b/langfuse/api/resources/dataset_items/types/paginated_dataset_items.py deleted file mode 100644 index 8592ba80f..000000000 --- a/langfuse/api/resources/dataset_items/types/paginated_dataset_items.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.dataset_item import DatasetItem -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class PaginatedDatasetItems(pydantic_v1.BaseModel): - data: typing.List[DatasetItem] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/dataset_run_items/__init__.py b/langfuse/api/resources/dataset_run_items/__init__.py deleted file mode 100644 index d522a3129..000000000 --- a/langfuse/api/resources/dataset_run_items/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import CreateDatasetRunItemRequest, PaginatedDatasetRunItems - -__all__ = ["CreateDatasetRunItemRequest", "PaginatedDatasetRunItems"] diff --git a/langfuse/api/resources/dataset_run_items/client.py b/langfuse/api/resources/dataset_run_items/client.py deleted file mode 100644 index 3664fde96..000000000 --- a/langfuse/api/resources/dataset_run_items/client.py +++ /dev/null @@ -1,366 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.dataset_run_item import DatasetRunItem -from .types.create_dataset_run_item_request import CreateDatasetRunItemRequest -from .types.paginated_dataset_run_items import PaginatedDatasetRunItems - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class DatasetRunItemsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def create( - self, - *, - request: CreateDatasetRunItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> DatasetRunItem: - """ - Create a dataset run item - - Parameters - ---------- - request : CreateDatasetRunItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetRunItem - - Examples - -------- - from langfuse import CreateDatasetRunItemRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.dataset_run_items.create( - request=CreateDatasetRunItemRequest( - run_name="runName", - dataset_item_id="datasetItemId", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/dataset-run-items", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetRunItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def list( - self, - *, - dataset_id: str, - run_name: str, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasetRunItems: - """ - List dataset run items - - Parameters - ---------- - dataset_id : str - - run_name : str - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasetRunItems - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.dataset_run_items.list( - dataset_id="datasetId", - run_name="runName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/dataset-run-items", - method="GET", - params={ - "datasetId": dataset_id, - "runName": run_name, - "page": page, - "limit": limit, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedDatasetRunItems, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncDatasetRunItemsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def create( - self, - *, - request: CreateDatasetRunItemRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> DatasetRunItem: - """ - Create a dataset run item - - Parameters - ---------- - request : CreateDatasetRunItemRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetRunItem - - Examples - -------- - import asyncio - - from langfuse import CreateDatasetRunItemRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.dataset_run_items.create( - request=CreateDatasetRunItemRequest( - run_name="runName", - dataset_item_id="datasetItemId", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/dataset-run-items", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetRunItem, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def list( - self, - *, - dataset_id: str, - run_name: str, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasetRunItems: - """ - List dataset run items - - Parameters - ---------- - dataset_id : str - - run_name : str - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasetRunItems - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.dataset_run_items.list( - dataset_id="datasetId", - run_name="runName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/dataset-run-items", - method="GET", - params={ - "datasetId": dataset_id, - "runName": run_name, - "page": page, - "limit": limit, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedDatasetRunItems, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/dataset_run_items/types/__init__.py b/langfuse/api/resources/dataset_run_items/types/__init__.py deleted file mode 100644 index e48e72c27..000000000 --- a/langfuse/api/resources/dataset_run_items/types/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_dataset_run_item_request import CreateDatasetRunItemRequest -from .paginated_dataset_run_items import PaginatedDatasetRunItems - -__all__ = ["CreateDatasetRunItemRequest", "PaginatedDatasetRunItems"] diff --git a/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py b/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py deleted file mode 100644 index 091f34e7e..000000000 --- a/langfuse/api/resources/dataset_run_items/types/create_dataset_run_item_request.py +++ /dev/null @@ -1,74 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateDatasetRunItemRequest(pydantic_v1.BaseModel): - run_name: str = pydantic_v1.Field(alias="runName") - run_description: typing.Optional[str] = pydantic_v1.Field( - alias="runDescription", default=None - ) - """ - Description of the run. If run exists, description will be updated. - """ - - metadata: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) - """ - Metadata of the dataset run, updates run if run already exists - """ - - dataset_item_id: str = pydantic_v1.Field(alias="datasetItemId") - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - """ - traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. - """ - - dataset_version: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="datasetVersion", default=None - ) - """ - ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., "2026-01-21T14:35:42Z"). - Specifies the dataset version to use for this experiment run. - If provided, the experiment will use dataset items as they existed at or before this timestamp. - If not provided, uses the latest version of dataset items. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/dataset_run_items/types/paginated_dataset_run_items.py b/langfuse/api/resources/dataset_run_items/types/paginated_dataset_run_items.py deleted file mode 100644 index c1611bae0..000000000 --- a/langfuse/api/resources/dataset_run_items/types/paginated_dataset_run_items.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.dataset_run_item import DatasetRunItem -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class PaginatedDatasetRunItems(pydantic_v1.BaseModel): - data: typing.List[DatasetRunItem] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/datasets/__init__.py b/langfuse/api/resources/datasets/__init__.py deleted file mode 100644 index dd30a359d..000000000 --- a/langfuse/api/resources/datasets/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - CreateDatasetRequest, - DeleteDatasetRunResponse, - PaginatedDatasetRuns, - PaginatedDatasets, -) - -__all__ = [ - "CreateDatasetRequest", - "DeleteDatasetRunResponse", - "PaginatedDatasetRuns", - "PaginatedDatasets", -] diff --git a/langfuse/api/resources/datasets/client.py b/langfuse/api/resources/datasets/client.py deleted file mode 100644 index aff7293a0..000000000 --- a/langfuse/api/resources/datasets/client.py +++ /dev/null @@ -1,942 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.dataset import Dataset -from ..commons.types.dataset_run_with_items import DatasetRunWithItems -from .types.create_dataset_request import CreateDatasetRequest -from .types.delete_dataset_run_response import DeleteDatasetRunResponse -from .types.paginated_dataset_runs import PaginatedDatasetRuns -from .types.paginated_datasets import PaginatedDatasets - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class DatasetsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasets: - """ - Get all datasets - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasets - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.datasets.list() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/datasets", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedDatasets, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get( - self, - dataset_name: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> Dataset: - """ - Get a dataset - - Parameters - ---------- - dataset_name : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Dataset - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.datasets.get( - dataset_name="datasetName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/v2/datasets/{jsonable_encoder(dataset_name)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Dataset, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create( - self, - *, - request: CreateDatasetRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> Dataset: - """ - Create a dataset - - Parameters - ---------- - request : CreateDatasetRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Dataset - - Examples - -------- - from langfuse import CreateDatasetRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.datasets.create( - request=CreateDatasetRequest( - name="name", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/datasets", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Dataset, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_run( - self, - dataset_name: str, - run_name: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> DatasetRunWithItems: - """ - Get a dataset run and its items - - Parameters - ---------- - dataset_name : str - - run_name : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetRunWithItems - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.datasets.get_run( - dataset_name="datasetName", - run_name="runName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetRunWithItems, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_run( - self, - dataset_name: str, - run_name: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> DeleteDatasetRunResponse: - """ - Delete a dataset run and all its run items. This action is irreversible. - - Parameters - ---------- - dataset_name : str - - run_name : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteDatasetRunResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.datasets.delete_run( - dataset_name="datasetName", - run_name="runName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteDatasetRunResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_runs( - self, - dataset_name: str, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasetRuns: - """ - Get dataset runs - - Parameters - ---------- - dataset_name : str - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasetRuns - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.datasets.get_runs( - dataset_name="datasetName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedDatasetRuns, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncDatasetsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasets: - """ - Get all datasets - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasets - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.datasets.list() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/datasets", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedDatasets, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get( - self, - dataset_name: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> Dataset: - """ - Get a dataset - - Parameters - ---------- - dataset_name : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Dataset - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.datasets.get( - dataset_name="datasetName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/v2/datasets/{jsonable_encoder(dataset_name)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Dataset, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create( - self, - *, - request: CreateDatasetRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> Dataset: - """ - Create a dataset - - Parameters - ---------- - request : CreateDatasetRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Dataset - - Examples - -------- - import asyncio - - from langfuse import CreateDatasetRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.datasets.create( - request=CreateDatasetRequest( - name="name", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/datasets", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Dataset, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_run( - self, - dataset_name: str, - run_name: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> DatasetRunWithItems: - """ - Get a dataset run and its items - - Parameters - ---------- - dataset_name : str - - run_name : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DatasetRunWithItems - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.datasets.get_run( - dataset_name="datasetName", - run_name="runName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DatasetRunWithItems, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_run( - self, - dataset_name: str, - run_name: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> DeleteDatasetRunResponse: - """ - Delete a dataset run and all its run items. This action is irreversible. - - Parameters - ---------- - dataset_name : str - - run_name : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - DeleteDatasetRunResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.datasets.delete_run( - dataset_name="datasetName", - run_name="runName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs/{jsonable_encoder(run_name)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - DeleteDatasetRunResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_runs( - self, - dataset_name: str, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedDatasetRuns: - """ - Get dataset runs - - Parameters - ---------- - dataset_name : str - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedDatasetRuns - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.datasets.get_runs( - dataset_name="datasetName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/datasets/{jsonable_encoder(dataset_name)}/runs", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedDatasetRuns, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/datasets/types/__init__.py b/langfuse/api/resources/datasets/types/__init__.py deleted file mode 100644 index f3304a59f..000000000 --- a/langfuse/api/resources/datasets/types/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_dataset_request import CreateDatasetRequest -from .delete_dataset_run_response import DeleteDatasetRunResponse -from .paginated_dataset_runs import PaginatedDatasetRuns -from .paginated_datasets import PaginatedDatasets - -__all__ = [ - "CreateDatasetRequest", - "DeleteDatasetRunResponse", - "PaginatedDatasetRuns", - "PaginatedDatasets", -] diff --git a/langfuse/api/resources/datasets/types/create_dataset_request.py b/langfuse/api/resources/datasets/types/create_dataset_request.py deleted file mode 100644 index 228527909..000000000 --- a/langfuse/api/resources/datasets/types/create_dataset_request.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateDatasetRequest(pydantic_v1.BaseModel): - name: str - description: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None - input_schema: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="inputSchema", default=None - ) - """ - JSON Schema for validating dataset item inputs. When set, all new and existing dataset items will be validated against this schema. - """ - - expected_output_schema: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="expectedOutputSchema", default=None - ) - """ - JSON Schema for validating dataset item expected outputs. When set, all new and existing dataset items will be validated against this schema. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/datasets/types/delete_dataset_run_response.py b/langfuse/api/resources/datasets/types/delete_dataset_run_response.py deleted file mode 100644 index cf52eca14..000000000 --- a/langfuse/api/resources/datasets/types/delete_dataset_run_response.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DeleteDatasetRunResponse(pydantic_v1.BaseModel): - message: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/datasets/types/paginated_dataset_runs.py b/langfuse/api/resources/datasets/types/paginated_dataset_runs.py deleted file mode 100644 index 86f2f0a73..000000000 --- a/langfuse/api/resources/datasets/types/paginated_dataset_runs.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.dataset_run import DatasetRun -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class PaginatedDatasetRuns(pydantic_v1.BaseModel): - data: typing.List[DatasetRun] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/datasets/types/paginated_datasets.py b/langfuse/api/resources/datasets/types/paginated_datasets.py deleted file mode 100644 index c2d436bf4..000000000 --- a/langfuse/api/resources/datasets/types/paginated_datasets.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.dataset import Dataset -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class PaginatedDatasets(pydantic_v1.BaseModel): - data: typing.List[Dataset] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/health/__init__.py b/langfuse/api/resources/health/__init__.py deleted file mode 100644 index f468cdffb..000000000 --- a/langfuse/api/resources/health/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import HealthResponse -from .errors import ServiceUnavailableError - -__all__ = ["HealthResponse", "ServiceUnavailableError"] diff --git a/langfuse/api/resources/health/client.py b/langfuse/api/resources/health/client.py deleted file mode 100644 index 029be7a0c..000000000 --- a/langfuse/api/resources/health/client.py +++ /dev/null @@ -1,154 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .errors.service_unavailable_error import ServiceUnavailableError -from .types.health_response import HealthResponse - - -class HealthClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def health( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HealthResponse: - """ - Check health of API and database - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HealthResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.health.health() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/health", method="GET", request_options=request_options - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(HealthResponse, _response.json()) # type: ignore - if _response.status_code == 503: - raise ServiceUnavailableError() - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncHealthClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def health( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HealthResponse: - """ - Check health of API and database - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HealthResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.health.health() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/health", method="GET", request_options=request_options - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(HealthResponse, _response.json()) # type: ignore - if _response.status_code == 503: - raise ServiceUnavailableError() - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/health/errors/__init__.py b/langfuse/api/resources/health/errors/__init__.py deleted file mode 100644 index 46bb3fedd..000000000 --- a/langfuse/api/resources/health/errors/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .service_unavailable_error import ServiceUnavailableError - -__all__ = ["ServiceUnavailableError"] diff --git a/langfuse/api/resources/health/errors/service_unavailable_error.py b/langfuse/api/resources/health/errors/service_unavailable_error.py deleted file mode 100644 index acfd8fbf3..000000000 --- a/langfuse/api/resources/health/errors/service_unavailable_error.py +++ /dev/null @@ -1,8 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from ....core.api_error import ApiError - - -class ServiceUnavailableError(ApiError): - def __init__(self) -> None: - super().__init__(status_code=503) diff --git a/langfuse/api/resources/health/types/__init__.py b/langfuse/api/resources/health/types/__init__.py deleted file mode 100644 index 5fb7ec574..000000000 --- a/langfuse/api/resources/health/types/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .health_response import HealthResponse - -__all__ = ["HealthResponse"] diff --git a/langfuse/api/resources/health/types/health_response.py b/langfuse/api/resources/health/types/health_response.py deleted file mode 100644 index 633da67a8..000000000 --- a/langfuse/api/resources/health/types/health_response.py +++ /dev/null @@ -1,58 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class HealthResponse(pydantic_v1.BaseModel): - """ - Examples - -------- - from langfuse import HealthResponse - - HealthResponse( - version="1.25.0", - status="OK", - ) - """ - - version: str = pydantic_v1.Field() - """ - Langfuse server version - """ - - status: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/__init__.py b/langfuse/api/resources/ingestion/__init__.py deleted file mode 100644 index 9e072dc17..000000000 --- a/langfuse/api/resources/ingestion/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - BaseEvent, - CreateEventBody, - CreateEventEvent, - CreateGenerationBody, - CreateGenerationEvent, - CreateObservationEvent, - CreateSpanBody, - CreateSpanEvent, - IngestionError, - IngestionEvent, - IngestionEvent_EventCreate, - IngestionEvent_GenerationCreate, - IngestionEvent_GenerationUpdate, - IngestionEvent_ObservationCreate, - IngestionEvent_ObservationUpdate, - IngestionEvent_ScoreCreate, - IngestionEvent_SdkLog, - IngestionEvent_SpanCreate, - IngestionEvent_SpanUpdate, - IngestionEvent_TraceCreate, - IngestionResponse, - IngestionSuccess, - IngestionUsage, - ObservationBody, - ObservationType, - OpenAiCompletionUsageSchema, - OpenAiResponseUsageSchema, - OpenAiUsage, - OptionalObservationBody, - ScoreBody, - ScoreEvent, - SdkLogBody, - SdkLogEvent, - TraceBody, - TraceEvent, - UpdateEventBody, - UpdateGenerationBody, - UpdateGenerationEvent, - UpdateObservationEvent, - UpdateSpanBody, - UpdateSpanEvent, - UsageDetails, -) - -__all__ = [ - "BaseEvent", - "CreateEventBody", - "CreateEventEvent", - "CreateGenerationBody", - "CreateGenerationEvent", - "CreateObservationEvent", - "CreateSpanBody", - "CreateSpanEvent", - "IngestionError", - "IngestionEvent", - "IngestionEvent_EventCreate", - "IngestionEvent_GenerationCreate", - "IngestionEvent_GenerationUpdate", - "IngestionEvent_ObservationCreate", - "IngestionEvent_ObservationUpdate", - "IngestionEvent_ScoreCreate", - "IngestionEvent_SdkLog", - "IngestionEvent_SpanCreate", - "IngestionEvent_SpanUpdate", - "IngestionEvent_TraceCreate", - "IngestionResponse", - "IngestionSuccess", - "IngestionUsage", - "ObservationBody", - "ObservationType", - "OpenAiCompletionUsageSchema", - "OpenAiResponseUsageSchema", - "OpenAiUsage", - "OptionalObservationBody", - "ScoreBody", - "ScoreEvent", - "SdkLogBody", - "SdkLogEvent", - "TraceBody", - "TraceEvent", - "UpdateEventBody", - "UpdateGenerationBody", - "UpdateGenerationEvent", - "UpdateObservationEvent", - "UpdateSpanBody", - "UpdateSpanEvent", - "UsageDetails", -] diff --git a/langfuse/api/resources/ingestion/types/__init__.py b/langfuse/api/resources/ingestion/types/__init__.py deleted file mode 100644 index a3490e4dc..000000000 --- a/langfuse/api/resources/ingestion/types/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .base_event import BaseEvent -from .create_event_body import CreateEventBody -from .create_event_event import CreateEventEvent -from .create_generation_body import CreateGenerationBody -from .create_generation_event import CreateGenerationEvent -from .create_observation_event import CreateObservationEvent -from .create_span_body import CreateSpanBody -from .create_span_event import CreateSpanEvent -from .ingestion_error import IngestionError -from .ingestion_event import ( - IngestionEvent, - IngestionEvent_EventCreate, - IngestionEvent_GenerationCreate, - IngestionEvent_GenerationUpdate, - IngestionEvent_ObservationCreate, - IngestionEvent_ObservationUpdate, - IngestionEvent_ScoreCreate, - IngestionEvent_SdkLog, - IngestionEvent_SpanCreate, - IngestionEvent_SpanUpdate, - IngestionEvent_TraceCreate, -) -from .ingestion_response import IngestionResponse -from .ingestion_success import IngestionSuccess -from .ingestion_usage import IngestionUsage -from .observation_body import ObservationBody -from .observation_type import ObservationType -from .open_ai_completion_usage_schema import OpenAiCompletionUsageSchema -from .open_ai_response_usage_schema import OpenAiResponseUsageSchema -from .open_ai_usage import OpenAiUsage -from .optional_observation_body import OptionalObservationBody -from .score_body import ScoreBody -from .score_event import ScoreEvent -from .sdk_log_body import SdkLogBody -from .sdk_log_event import SdkLogEvent -from .trace_body import TraceBody -from .trace_event import TraceEvent -from .update_event_body import UpdateEventBody -from .update_generation_body import UpdateGenerationBody -from .update_generation_event import UpdateGenerationEvent -from .update_observation_event import UpdateObservationEvent -from .update_span_body import UpdateSpanBody -from .update_span_event import UpdateSpanEvent -from .usage_details import UsageDetails - -__all__ = [ - "BaseEvent", - "CreateEventBody", - "CreateEventEvent", - "CreateGenerationBody", - "CreateGenerationEvent", - "CreateObservationEvent", - "CreateSpanBody", - "CreateSpanEvent", - "IngestionError", - "IngestionEvent", - "IngestionEvent_EventCreate", - "IngestionEvent_GenerationCreate", - "IngestionEvent_GenerationUpdate", - "IngestionEvent_ObservationCreate", - "IngestionEvent_ObservationUpdate", - "IngestionEvent_ScoreCreate", - "IngestionEvent_SdkLog", - "IngestionEvent_SpanCreate", - "IngestionEvent_SpanUpdate", - "IngestionEvent_TraceCreate", - "IngestionResponse", - "IngestionSuccess", - "IngestionUsage", - "ObservationBody", - "ObservationType", - "OpenAiCompletionUsageSchema", - "OpenAiResponseUsageSchema", - "OpenAiUsage", - "OptionalObservationBody", - "ScoreBody", - "ScoreEvent", - "SdkLogBody", - "SdkLogEvent", - "TraceBody", - "TraceEvent", - "UpdateEventBody", - "UpdateGenerationBody", - "UpdateGenerationEvent", - "UpdateObservationEvent", - "UpdateSpanBody", - "UpdateSpanEvent", - "UsageDetails", -] diff --git a/langfuse/api/resources/ingestion/types/base_event.py b/langfuse/api/resources/ingestion/types/base_event.py deleted file mode 100644 index dec8a52e7..000000000 --- a/langfuse/api/resources/ingestion/types/base_event.py +++ /dev/null @@ -1,55 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class BaseEvent(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - UUID v4 that identifies the event - """ - - timestamp: str = pydantic_v1.Field() - """ - Datetime (ISO 8601) of event creation in client. Should be as close to actual event creation in client as possible, this timestamp will be used for ordering of events in future release. Resolution: milliseconds (required), microseconds (optimal). - """ - - metadata: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) - """ - Optional. Metadata field used by the Langfuse SDKs for debugging. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_event_body.py b/langfuse/api/resources/ingestion/types/create_event_body.py deleted file mode 100644 index afe8677f3..000000000 --- a/langfuse/api/resources/ingestion/types/create_event_body.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .optional_observation_body import OptionalObservationBody - - -class CreateEventBody(OptionalObservationBody): - id: typing.Optional[str] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_event_event.py b/langfuse/api/resources/ingestion/types/create_event_event.py deleted file mode 100644 index 0c3cce040..000000000 --- a/langfuse/api/resources/ingestion/types/create_event_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .create_event_body import CreateEventBody - - -class CreateEventEvent(BaseEvent): - body: CreateEventBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_generation_body.py b/langfuse/api/resources/ingestion/types/create_generation_body.py deleted file mode 100644 index 428b58607..000000000 --- a/langfuse/api/resources/ingestion/types/create_generation_body.py +++ /dev/null @@ -1,67 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.map_value import MapValue -from .create_span_body import CreateSpanBody -from .ingestion_usage import IngestionUsage -from .usage_details import UsageDetails - - -class CreateGenerationBody(CreateSpanBody): - completion_start_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="completionStartTime", default=None - ) - model: typing.Optional[str] = None - model_parameters: typing.Optional[typing.Dict[str, MapValue]] = pydantic_v1.Field( - alias="modelParameters", default=None - ) - usage: typing.Optional[IngestionUsage] = None - usage_details: typing.Optional[UsageDetails] = pydantic_v1.Field( - alias="usageDetails", default=None - ) - cost_details: typing.Optional[typing.Dict[str, float]] = pydantic_v1.Field( - alias="costDetails", default=None - ) - prompt_name: typing.Optional[str] = pydantic_v1.Field( - alias="promptName", default=None - ) - prompt_version: typing.Optional[int] = pydantic_v1.Field( - alias="promptVersion", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_generation_event.py b/langfuse/api/resources/ingestion/types/create_generation_event.py deleted file mode 100644 index cb7b484dd..000000000 --- a/langfuse/api/resources/ingestion/types/create_generation_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .create_generation_body import CreateGenerationBody - - -class CreateGenerationEvent(BaseEvent): - body: CreateGenerationBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_observation_event.py b/langfuse/api/resources/ingestion/types/create_observation_event.py deleted file mode 100644 index adfefc793..000000000 --- a/langfuse/api/resources/ingestion/types/create_observation_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .observation_body import ObservationBody - - -class CreateObservationEvent(BaseEvent): - body: ObservationBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_span_body.py b/langfuse/api/resources/ingestion/types/create_span_body.py deleted file mode 100644 index c31fde567..000000000 --- a/langfuse/api/resources/ingestion/types/create_span_body.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .create_event_body import CreateEventBody - - -class CreateSpanBody(CreateEventBody): - end_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="endTime", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/create_span_event.py b/langfuse/api/resources/ingestion/types/create_span_event.py deleted file mode 100644 index 7a8e8154c..000000000 --- a/langfuse/api/resources/ingestion/types/create_span_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .create_span_body import CreateSpanBody - - -class CreateSpanEvent(BaseEvent): - body: CreateSpanBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/ingestion_error.py b/langfuse/api/resources/ingestion/types/ingestion_error.py deleted file mode 100644 index b9028ce1d..000000000 --- a/langfuse/api/resources/ingestion/types/ingestion_error.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class IngestionError(pydantic_v1.BaseModel): - id: str - status: int - message: typing.Optional[str] = None - error: typing.Optional[typing.Any] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/ingestion_event.py b/langfuse/api/resources/ingestion/types/ingestion_event.py deleted file mode 100644 index e083c9354..000000000 --- a/langfuse/api/resources/ingestion/types/ingestion_event.py +++ /dev/null @@ -1,422 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .create_event_body import CreateEventBody -from .create_generation_body import CreateGenerationBody -from .create_span_body import CreateSpanBody -from .observation_body import ObservationBody -from .score_body import ScoreBody -from .sdk_log_body import SdkLogBody -from .trace_body import TraceBody -from .update_generation_body import UpdateGenerationBody -from .update_span_body import UpdateSpanBody - - -class IngestionEvent_TraceCreate(pydantic_v1.BaseModel): - body: TraceBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["trace-create"] = "trace-create" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_ScoreCreate(pydantic_v1.BaseModel): - body: ScoreBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["score-create"] = "score-create" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_SpanCreate(pydantic_v1.BaseModel): - body: CreateSpanBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["span-create"] = "span-create" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_SpanUpdate(pydantic_v1.BaseModel): - body: UpdateSpanBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["span-update"] = "span-update" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_GenerationCreate(pydantic_v1.BaseModel): - body: CreateGenerationBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["generation-create"] = "generation-create" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_GenerationUpdate(pydantic_v1.BaseModel): - body: UpdateGenerationBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["generation-update"] = "generation-update" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_EventCreate(pydantic_v1.BaseModel): - body: CreateEventBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["event-create"] = "event-create" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_SdkLog(pydantic_v1.BaseModel): - body: SdkLogBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["sdk-log"] = "sdk-log" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_ObservationCreate(pydantic_v1.BaseModel): - body: ObservationBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["observation-create"] = "observation-create" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class IngestionEvent_ObservationUpdate(pydantic_v1.BaseModel): - body: ObservationBody - id: str - timestamp: str - metadata: typing.Optional[typing.Any] = None - type: typing.Literal["observation-update"] = "observation-update" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -IngestionEvent = typing.Union[ - IngestionEvent_TraceCreate, - IngestionEvent_ScoreCreate, - IngestionEvent_SpanCreate, - IngestionEvent_SpanUpdate, - IngestionEvent_GenerationCreate, - IngestionEvent_GenerationUpdate, - IngestionEvent_EventCreate, - IngestionEvent_SdkLog, - IngestionEvent_ObservationCreate, - IngestionEvent_ObservationUpdate, -] diff --git a/langfuse/api/resources/ingestion/types/ingestion_response.py b/langfuse/api/resources/ingestion/types/ingestion_response.py deleted file mode 100644 index b4e66349c..000000000 --- a/langfuse/api/resources/ingestion/types/ingestion_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .ingestion_error import IngestionError -from .ingestion_success import IngestionSuccess - - -class IngestionResponse(pydantic_v1.BaseModel): - successes: typing.List[IngestionSuccess] - errors: typing.List[IngestionError] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/ingestion_success.py b/langfuse/api/resources/ingestion/types/ingestion_success.py deleted file mode 100644 index 481e64752..000000000 --- a/langfuse/api/resources/ingestion/types/ingestion_success.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class IngestionSuccess(pydantic_v1.BaseModel): - id: str - status: int - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/observation_body.py b/langfuse/api/resources/ingestion/types/observation_body.py deleted file mode 100644 index d191a1f12..000000000 --- a/langfuse/api/resources/ingestion/types/observation_body.py +++ /dev/null @@ -1,77 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.map_value import MapValue -from ...commons.types.observation_level import ObservationLevel -from ...commons.types.usage import Usage -from .observation_type import ObservationType - - -class ObservationBody(pydantic_v1.BaseModel): - id: typing.Optional[str] = None - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - type: ObservationType - name: typing.Optional[str] = None - start_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="startTime", default=None - ) - end_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="endTime", default=None - ) - completion_start_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="completionStartTime", default=None - ) - model: typing.Optional[str] = None - model_parameters: typing.Optional[typing.Dict[str, MapValue]] = pydantic_v1.Field( - alias="modelParameters", default=None - ) - input: typing.Optional[typing.Any] = None - version: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None - output: typing.Optional[typing.Any] = None - usage: typing.Optional[Usage] = None - level: typing.Optional[ObservationLevel] = None - status_message: typing.Optional[str] = pydantic_v1.Field( - alias="statusMessage", default=None - ) - parent_observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="parentObservationId", default=None - ) - environment: typing.Optional[str] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/open_ai_completion_usage_schema.py b/langfuse/api/resources/ingestion/types/open_ai_completion_usage_schema.py deleted file mode 100644 index 368a7da03..000000000 --- a/langfuse/api/resources/ingestion/types/open_ai_completion_usage_schema.py +++ /dev/null @@ -1,54 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OpenAiCompletionUsageSchema(pydantic_v1.BaseModel): - """ - OpenAI Usage schema from (Chat-)Completion APIs - """ - - prompt_tokens: int - completion_tokens: int - total_tokens: int - prompt_tokens_details: typing.Optional[typing.Dict[str, typing.Optional[int]]] = ( - None - ) - completion_tokens_details: typing.Optional[ - typing.Dict[str, typing.Optional[int]] - ] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/open_ai_response_usage_schema.py b/langfuse/api/resources/ingestion/types/open_ai_response_usage_schema.py deleted file mode 100644 index 0c68e6a7d..000000000 --- a/langfuse/api/resources/ingestion/types/open_ai_response_usage_schema.py +++ /dev/null @@ -1,52 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OpenAiResponseUsageSchema(pydantic_v1.BaseModel): - """ - OpenAI Usage schema from Response API - """ - - input_tokens: int - output_tokens: int - total_tokens: int - input_tokens_details: typing.Optional[typing.Dict[str, typing.Optional[int]]] = None - output_tokens_details: typing.Optional[typing.Dict[str, typing.Optional[int]]] = ( - None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/open_ai_usage.py b/langfuse/api/resources/ingestion/types/open_ai_usage.py deleted file mode 100644 index 86e7ebd82..000000000 --- a/langfuse/api/resources/ingestion/types/open_ai_usage.py +++ /dev/null @@ -1,56 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OpenAiUsage(pydantic_v1.BaseModel): - """ - Usage interface of OpenAI for improved compatibility. - """ - - prompt_tokens: typing.Optional[int] = pydantic_v1.Field( - alias="promptTokens", default=None - ) - completion_tokens: typing.Optional[int] = pydantic_v1.Field( - alias="completionTokens", default=None - ) - total_tokens: typing.Optional[int] = pydantic_v1.Field( - alias="totalTokens", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/optional_observation_body.py b/langfuse/api/resources/ingestion/types/optional_observation_body.py deleted file mode 100644 index 7302d30f9..000000000 --- a/langfuse/api/resources/ingestion/types/optional_observation_body.py +++ /dev/null @@ -1,61 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.observation_level import ObservationLevel - - -class OptionalObservationBody(pydantic_v1.BaseModel): - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - name: typing.Optional[str] = None - start_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="startTime", default=None - ) - metadata: typing.Optional[typing.Any] = None - input: typing.Optional[typing.Any] = None - output: typing.Optional[typing.Any] = None - level: typing.Optional[ObservationLevel] = None - status_message: typing.Optional[str] = pydantic_v1.Field( - alias="statusMessage", default=None - ) - parent_observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="parentObservationId", default=None - ) - version: typing.Optional[str] = None - environment: typing.Optional[str] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/score_body.py b/langfuse/api/resources/ingestion/types/score_body.py deleted file mode 100644 index 8e72a5682..000000000 --- a/langfuse/api/resources/ingestion/types/score_body.py +++ /dev/null @@ -1,97 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.create_score_value import CreateScoreValue -from ...commons.types.score_data_type import ScoreDataType - - -class ScoreBody(pydantic_v1.BaseModel): - """ - Examples - -------- - from langfuse import ScoreBody - - ScoreBody( - name="novelty", - value=0.9, - trace_id="cdef-1234-5678-90ab", - ) - """ - - id: typing.Optional[str] = None - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str = pydantic_v1.Field() - """ - The name of the score. Always overrides "output" for correction scores. - """ - - environment: typing.Optional[str] = None - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - """ - The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. - """ - - value: CreateScoreValue = pydantic_v1.Field() - """ - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) - """ - - comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None - data_type: typing.Optional[ScoreDataType] = pydantic_v1.Field( - alias="dataType", default=None - ) - """ - When set, must match the score value's type. If not set, will be inferred from the score value or config - """ - - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - """ - Reference a score config on a score. When set, the score name must equal the config name and scores must comply with the config's range and data type. For categorical scores, the value must map to a config category. Numeric scores might be constrained by the score config's max and min values - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/score_event.py b/langfuse/api/resources/ingestion/types/score_event.py deleted file mode 100644 index ea05aedef..000000000 --- a/langfuse/api/resources/ingestion/types/score_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .score_body import ScoreBody - - -class ScoreEvent(BaseEvent): - body: ScoreBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/sdk_log_body.py b/langfuse/api/resources/ingestion/types/sdk_log_body.py deleted file mode 100644 index df8972860..000000000 --- a/langfuse/api/resources/ingestion/types/sdk_log_body.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class SdkLogBody(pydantic_v1.BaseModel): - log: typing.Any - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/sdk_log_event.py b/langfuse/api/resources/ingestion/types/sdk_log_event.py deleted file mode 100644 index d7ad87de8..000000000 --- a/langfuse/api/resources/ingestion/types/sdk_log_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .sdk_log_body import SdkLogBody - - -class SdkLogEvent(BaseEvent): - body: SdkLogBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/trace_body.py b/langfuse/api/resources/ingestion/types/trace_body.py deleted file mode 100644 index 3f5550435..000000000 --- a/langfuse/api/resources/ingestion/types/trace_body.py +++ /dev/null @@ -1,61 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class TraceBody(pydantic_v1.BaseModel): - id: typing.Optional[str] = None - timestamp: typing.Optional[dt.datetime] = None - name: typing.Optional[str] = None - user_id: typing.Optional[str] = pydantic_v1.Field(alias="userId", default=None) - input: typing.Optional[typing.Any] = None - output: typing.Optional[typing.Any] = None - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - release: typing.Optional[str] = None - version: typing.Optional[str] = None - metadata: typing.Optional[typing.Any] = None - tags: typing.Optional[typing.List[str]] = None - environment: typing.Optional[str] = None - public: typing.Optional[bool] = pydantic_v1.Field(default=None) - """ - Make trace publicly accessible via url - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/trace_event.py b/langfuse/api/resources/ingestion/types/trace_event.py deleted file mode 100644 index b84ddd615..000000000 --- a/langfuse/api/resources/ingestion/types/trace_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .trace_body import TraceBody - - -class TraceEvent(BaseEvent): - body: TraceBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/update_event_body.py b/langfuse/api/resources/ingestion/types/update_event_body.py deleted file mode 100644 index 35bbb359b..000000000 --- a/langfuse/api/resources/ingestion/types/update_event_body.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .optional_observation_body import OptionalObservationBody - - -class UpdateEventBody(OptionalObservationBody): - id: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/update_generation_body.py b/langfuse/api/resources/ingestion/types/update_generation_body.py deleted file mode 100644 index 2058543af..000000000 --- a/langfuse/api/resources/ingestion/types/update_generation_body.py +++ /dev/null @@ -1,67 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.map_value import MapValue -from .ingestion_usage import IngestionUsage -from .update_span_body import UpdateSpanBody -from .usage_details import UsageDetails - - -class UpdateGenerationBody(UpdateSpanBody): - completion_start_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="completionStartTime", default=None - ) - model: typing.Optional[str] = None - model_parameters: typing.Optional[typing.Dict[str, MapValue]] = pydantic_v1.Field( - alias="modelParameters", default=None - ) - usage: typing.Optional[IngestionUsage] = None - prompt_name: typing.Optional[str] = pydantic_v1.Field( - alias="promptName", default=None - ) - usage_details: typing.Optional[UsageDetails] = pydantic_v1.Field( - alias="usageDetails", default=None - ) - cost_details: typing.Optional[typing.Dict[str, float]] = pydantic_v1.Field( - alias="costDetails", default=None - ) - prompt_version: typing.Optional[int] = pydantic_v1.Field( - alias="promptVersion", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/update_generation_event.py b/langfuse/api/resources/ingestion/types/update_generation_event.py deleted file mode 100644 index da8f6a9fa..000000000 --- a/langfuse/api/resources/ingestion/types/update_generation_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .update_generation_body import UpdateGenerationBody - - -class UpdateGenerationEvent(BaseEvent): - body: UpdateGenerationBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/update_observation_event.py b/langfuse/api/resources/ingestion/types/update_observation_event.py deleted file mode 100644 index 9d7af357f..000000000 --- a/langfuse/api/resources/ingestion/types/update_observation_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .observation_body import ObservationBody - - -class UpdateObservationEvent(BaseEvent): - body: ObservationBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/update_span_body.py b/langfuse/api/resources/ingestion/types/update_span_body.py deleted file mode 100644 index e3484879b..000000000 --- a/langfuse/api/resources/ingestion/types/update_span_body.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .update_event_body import UpdateEventBody - - -class UpdateSpanBody(UpdateEventBody): - end_time: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="endTime", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/ingestion/types/update_span_event.py b/langfuse/api/resources/ingestion/types/update_span_event.py deleted file mode 100644 index ec7d83b15..000000000 --- a/langfuse/api/resources/ingestion/types/update_span_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_event import BaseEvent -from .update_span_body import UpdateSpanBody - - -class UpdateSpanEvent(BaseEvent): - body: UpdateSpanBody - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/llm_connections/__init__.py b/langfuse/api/resources/llm_connections/__init__.py deleted file mode 100644 index 3cf778f1b..000000000 --- a/langfuse/api/resources/llm_connections/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - LlmAdapter, - LlmConnection, - PaginatedLlmConnections, - UpsertLlmConnectionRequest, -) - -__all__ = [ - "LlmAdapter", - "LlmConnection", - "PaginatedLlmConnections", - "UpsertLlmConnectionRequest", -] diff --git a/langfuse/api/resources/llm_connections/client.py b/langfuse/api/resources/llm_connections/client.py deleted file mode 100644 index 4497598c5..000000000 --- a/langfuse/api/resources/llm_connections/client.py +++ /dev/null @@ -1,340 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.llm_connection import LlmConnection -from .types.paginated_llm_connections import PaginatedLlmConnections -from .types.upsert_llm_connection_request import UpsertLlmConnectionRequest - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class LlmConnectionsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedLlmConnections: - """ - Get all LLM connections in a project - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedLlmConnections - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.llm_connections.list() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/llm-connections", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedLlmConnections, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def upsert( - self, - *, - request: UpsertLlmConnectionRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> LlmConnection: - """ - Create or update an LLM connection. The connection is upserted on provider. - - Parameters - ---------- - request : UpsertLlmConnectionRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - LlmConnection - - Examples - -------- - from langfuse import LlmAdapter, UpsertLlmConnectionRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.llm_connections.upsert( - request=UpsertLlmConnectionRequest( - provider="provider", - adapter=LlmAdapter.ANTHROPIC, - secret_key="secretKey", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/llm-connections", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(LlmConnection, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncLlmConnectionsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedLlmConnections: - """ - Get all LLM connections in a project - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedLlmConnections - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.llm_connections.list() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/llm-connections", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PaginatedLlmConnections, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def upsert( - self, - *, - request: UpsertLlmConnectionRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> LlmConnection: - """ - Create or update an LLM connection. The connection is upserted on provider. - - Parameters - ---------- - request : UpsertLlmConnectionRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - LlmConnection - - Examples - -------- - import asyncio - - from langfuse import LlmAdapter, UpsertLlmConnectionRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.llm_connections.upsert( - request=UpsertLlmConnectionRequest( - provider="provider", - adapter=LlmAdapter.ANTHROPIC, - secret_key="secretKey", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/llm-connections", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(LlmConnection, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/llm_connections/types/__init__.py b/langfuse/api/resources/llm_connections/types/__init__.py deleted file mode 100644 index b490e6e27..000000000 --- a/langfuse/api/resources/llm_connections/types/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .llm_adapter import LlmAdapter -from .llm_connection import LlmConnection -from .paginated_llm_connections import PaginatedLlmConnections -from .upsert_llm_connection_request import UpsertLlmConnectionRequest - -__all__ = [ - "LlmAdapter", - "LlmConnection", - "PaginatedLlmConnections", - "UpsertLlmConnectionRequest", -] diff --git a/langfuse/api/resources/llm_connections/types/llm_connection.py b/langfuse/api/resources/llm_connections/types/llm_connection.py deleted file mode 100644 index 6ded1459a..000000000 --- a/langfuse/api/resources/llm_connections/types/llm_connection.py +++ /dev/null @@ -1,92 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class LlmConnection(pydantic_v1.BaseModel): - """ - LLM API connection configuration (secrets excluded) - """ - - id: str - provider: str = pydantic_v1.Field() - """ - Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. - """ - - adapter: str = pydantic_v1.Field() - """ - The adapter used to interface with the LLM - """ - - display_secret_key: str = pydantic_v1.Field(alias="displaySecretKey") - """ - Masked version of the secret key for display purposes - """ - - base_url: typing.Optional[str] = pydantic_v1.Field(alias="baseURL", default=None) - """ - Custom base URL for the LLM API - """ - - custom_models: typing.List[str] = pydantic_v1.Field(alias="customModels") - """ - List of custom model names available for this connection - """ - - with_default_models: bool = pydantic_v1.Field(alias="withDefaultModels") - """ - Whether to include default models for this adapter - """ - - extra_header_keys: typing.List[str] = pydantic_v1.Field(alias="extraHeaderKeys") - """ - Keys of extra headers sent with requests (values excluded for security) - """ - - config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( - default=None - ) - """ - Adapter-specific configuration. Required for Bedrock (`{"region":"us-east-1"}`), optional for VertexAI (`{"location":"us-central1"}`), not used by other adapters. - """ - - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/llm_connections/types/paginated_llm_connections.py b/langfuse/api/resources/llm_connections/types/paginated_llm_connections.py deleted file mode 100644 index 986dbb0bb..000000000 --- a/langfuse/api/resources/llm_connections/types/paginated_llm_connections.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...utils.resources.pagination.types.meta_response import MetaResponse -from .llm_connection import LlmConnection - - -class PaginatedLlmConnections(pydantic_v1.BaseModel): - data: typing.List[LlmConnection] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py b/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py deleted file mode 100644 index 7490f25b7..000000000 --- a/langfuse/api/resources/llm_connections/types/upsert_llm_connection_request.py +++ /dev/null @@ -1,95 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .llm_adapter import LlmAdapter - - -class UpsertLlmConnectionRequest(pydantic_v1.BaseModel): - """ - Request to create or update an LLM connection (upsert) - """ - - provider: str = pydantic_v1.Field() - """ - Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting. - """ - - adapter: LlmAdapter = pydantic_v1.Field() - """ - The adapter used to interface with the LLM - """ - - secret_key: str = pydantic_v1.Field(alias="secretKey") - """ - Secret key for the LLM API. - """ - - base_url: typing.Optional[str] = pydantic_v1.Field(alias="baseURL", default=None) - """ - Custom base URL for the LLM API - """ - - custom_models: typing.Optional[typing.List[str]] = pydantic_v1.Field( - alias="customModels", default=None - ) - """ - List of custom model names - """ - - with_default_models: typing.Optional[bool] = pydantic_v1.Field( - alias="withDefaultModels", default=None - ) - """ - Whether to include default models. Default is true. - """ - - extra_headers: typing.Optional[typing.Dict[str, str]] = pydantic_v1.Field( - alias="extraHeaders", default=None - ) - """ - Extra headers to send with requests - """ - - config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( - default=None - ) - """ - Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/media/__init__.py b/langfuse/api/resources/media/__init__.py deleted file mode 100644 index f337d7a04..000000000 --- a/langfuse/api/resources/media/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - GetMediaResponse, - GetMediaUploadUrlRequest, - GetMediaUploadUrlResponse, - MediaContentType, - PatchMediaBody, -) - -__all__ = [ - "GetMediaResponse", - "GetMediaUploadUrlRequest", - "GetMediaUploadUrlResponse", - "MediaContentType", - "PatchMediaBody", -] diff --git a/langfuse/api/resources/media/client.py b/langfuse/api/resources/media/client.py deleted file mode 100644 index bb8e4b149..000000000 --- a/langfuse/api/resources/media/client.py +++ /dev/null @@ -1,505 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.get_media_response import GetMediaResponse -from .types.get_media_upload_url_request import GetMediaUploadUrlRequest -from .types.get_media_upload_url_response import GetMediaUploadUrlResponse -from .types.patch_media_body import PatchMediaBody - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class MediaClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get( - self, media_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> GetMediaResponse: - """ - Get a media record - - Parameters - ---------- - media_id : str - The unique langfuse identifier of a media record - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - GetMediaResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.media.get( - media_id="mediaId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/media/{jsonable_encoder(media_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(GetMediaResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def patch( - self, - media_id: str, - *, - request: PatchMediaBody, - request_options: typing.Optional[RequestOptions] = None, - ) -> None: - """ - Patch a media record - - Parameters - ---------- - media_id : str - The unique langfuse identifier of a media record - - request : PatchMediaBody - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - import datetime - - from langfuse import PatchMediaBody - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.media.patch( - media_id="mediaId", - request=PatchMediaBody( - uploaded_at=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - upload_http_status=1, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/media/{jsonable_encoder(media_id)}", - method="PATCH", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_upload_url( - self, - *, - request: GetMediaUploadUrlRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> GetMediaUploadUrlResponse: - """ - Get a presigned upload URL for a media record - - Parameters - ---------- - request : GetMediaUploadUrlRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - GetMediaUploadUrlResponse - - Examples - -------- - from langfuse import GetMediaUploadUrlRequest, MediaContentType - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.media.get_upload_url( - request=GetMediaUploadUrlRequest( - trace_id="traceId", - content_type=MediaContentType.IMAGE_PNG, - content_length=1, - sha_256_hash="sha256Hash", - field="field", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/media", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - GetMediaUploadUrlResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncMediaClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get( - self, media_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> GetMediaResponse: - """ - Get a media record - - Parameters - ---------- - media_id : str - The unique langfuse identifier of a media record - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - GetMediaResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.media.get( - media_id="mediaId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/media/{jsonable_encoder(media_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(GetMediaResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def patch( - self, - media_id: str, - *, - request: PatchMediaBody, - request_options: typing.Optional[RequestOptions] = None, - ) -> None: - """ - Patch a media record - - Parameters - ---------- - media_id : str - The unique langfuse identifier of a media record - - request : PatchMediaBody - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - import asyncio - import datetime - - from langfuse import PatchMediaBody - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.media.patch( - media_id="mediaId", - request=PatchMediaBody( - uploaded_at=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - upload_http_status=1, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/media/{jsonable_encoder(media_id)}", - method="PATCH", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_upload_url( - self, - *, - request: GetMediaUploadUrlRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> GetMediaUploadUrlResponse: - """ - Get a presigned upload URL for a media record - - Parameters - ---------- - request : GetMediaUploadUrlRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - GetMediaUploadUrlResponse - - Examples - -------- - import asyncio - - from langfuse import GetMediaUploadUrlRequest, MediaContentType - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.media.get_upload_url( - request=GetMediaUploadUrlRequest( - trace_id="traceId", - content_type=MediaContentType.IMAGE_PNG, - content_length=1, - sha_256_hash="sha256Hash", - field="field", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/media", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - GetMediaUploadUrlResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/media/types/__init__.py b/langfuse/api/resources/media/types/__init__.py deleted file mode 100644 index 20af676d8..000000000 --- a/langfuse/api/resources/media/types/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .get_media_response import GetMediaResponse -from .get_media_upload_url_request import GetMediaUploadUrlRequest -from .get_media_upload_url_response import GetMediaUploadUrlResponse -from .media_content_type import MediaContentType -from .patch_media_body import PatchMediaBody - -__all__ = [ - "GetMediaResponse", - "GetMediaUploadUrlRequest", - "GetMediaUploadUrlResponse", - "MediaContentType", - "PatchMediaBody", -] diff --git a/langfuse/api/resources/media/types/get_media_response.py b/langfuse/api/resources/media/types/get_media_response.py deleted file mode 100644 index fa5368872..000000000 --- a/langfuse/api/resources/media/types/get_media_response.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class GetMediaResponse(pydantic_v1.BaseModel): - media_id: str = pydantic_v1.Field(alias="mediaId") - """ - The unique langfuse identifier of a media record - """ - - content_type: str = pydantic_v1.Field(alias="contentType") - """ - The MIME type of the media record - """ - - content_length: int = pydantic_v1.Field(alias="contentLength") - """ - The size of the media record in bytes - """ - - uploaded_at: dt.datetime = pydantic_v1.Field(alias="uploadedAt") - """ - The date and time when the media record was uploaded - """ - - url: str = pydantic_v1.Field() - """ - The download URL of the media record - """ - - url_expiry: str = pydantic_v1.Field(alias="urlExpiry") - """ - The expiry date and time of the media record download URL - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/media/types/get_media_upload_url_request.py b/langfuse/api/resources/media/types/get_media_upload_url_request.py deleted file mode 100644 index d0cde59fe..000000000 --- a/langfuse/api/resources/media/types/get_media_upload_url_request.py +++ /dev/null @@ -1,71 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .media_content_type import MediaContentType - - -class GetMediaUploadUrlRequest(pydantic_v1.BaseModel): - trace_id: str = pydantic_v1.Field(alias="traceId") - """ - The trace ID associated with the media record - """ - - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - """ - The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. - """ - - content_type: MediaContentType = pydantic_v1.Field(alias="contentType") - content_length: int = pydantic_v1.Field(alias="contentLength") - """ - The size of the media record in bytes - """ - - sha_256_hash: str = pydantic_v1.Field(alias="sha256Hash") - """ - The SHA-256 hash of the media record - """ - - field: str = pydantic_v1.Field() - """ - The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/media/types/get_media_upload_url_response.py b/langfuse/api/resources/media/types/get_media_upload_url_response.py deleted file mode 100644 index fadc76c01..000000000 --- a/langfuse/api/resources/media/types/get_media_upload_url_response.py +++ /dev/null @@ -1,54 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class GetMediaUploadUrlResponse(pydantic_v1.BaseModel): - upload_url: typing.Optional[str] = pydantic_v1.Field( - alias="uploadUrl", default=None - ) - """ - The presigned upload URL. If the asset is already uploaded, this will be null - """ - - media_id: str = pydantic_v1.Field(alias="mediaId") - """ - The unique langfuse identifier of a media record - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/media/types/patch_media_body.py b/langfuse/api/resources/media/types/patch_media_body.py deleted file mode 100644 index 49f0c3432..000000000 --- a/langfuse/api/resources/media/types/patch_media_body.py +++ /dev/null @@ -1,66 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class PatchMediaBody(pydantic_v1.BaseModel): - uploaded_at: dt.datetime = pydantic_v1.Field(alias="uploadedAt") - """ - The date and time when the media record was uploaded - """ - - upload_http_status: int = pydantic_v1.Field(alias="uploadHttpStatus") - """ - The HTTP status code of the upload - """ - - upload_http_error: typing.Optional[str] = pydantic_v1.Field( - alias="uploadHttpError", default=None - ) - """ - The HTTP error message of the upload - """ - - upload_time_ms: typing.Optional[int] = pydantic_v1.Field( - alias="uploadTimeMs", default=None - ) - """ - The time in milliseconds it took to upload the media record - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/metrics/__init__.py b/langfuse/api/resources/metrics/__init__.py deleted file mode 100644 index 90e510b5f..000000000 --- a/langfuse/api/resources/metrics/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import MetricsResponse - -__all__ = ["MetricsResponse"] diff --git a/langfuse/api/resources/metrics/types/__init__.py b/langfuse/api/resources/metrics/types/__init__.py deleted file mode 100644 index 7bf03027d..000000000 --- a/langfuse/api/resources/metrics/types/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .metrics_response import MetricsResponse - -__all__ = ["MetricsResponse"] diff --git a/langfuse/api/resources/metrics/types/metrics_response.py b/langfuse/api/resources/metrics/types/metrics_response.py deleted file mode 100644 index af0121c84..000000000 --- a/langfuse/api/resources/metrics/types/metrics_response.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class MetricsResponse(pydantic_v1.BaseModel): - data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() - """ - The metrics data. Each item in the list contains the metric values and dimensions requested in the query. - Format varies based on the query parameters. - Histograms will return an array with [lower, upper, height] tuples. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/metrics_v_2/__init__.py b/langfuse/api/resources/metrics_v_2/__init__.py deleted file mode 100644 index a8c9304a6..000000000 --- a/langfuse/api/resources/metrics_v_2/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import MetricsV2Response - -__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/types/__init__.py b/langfuse/api/resources/metrics_v_2/types/__init__.py deleted file mode 100644 index b77cf3d4d..000000000 --- a/langfuse/api/resources/metrics_v_2/types/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .metrics_v_2_response import MetricsV2Response - -__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py b/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py deleted file mode 100644 index ff0a475ea..000000000 --- a/langfuse/api/resources/metrics_v_2/types/metrics_v_2_response.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class MetricsV2Response(pydantic_v1.BaseModel): - data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() - """ - The metrics data. Each item in the list contains the metric values and dimensions requested in the query. - Format varies based on the query parameters. - Histograms will return an array with [lower, upper, height] tuples. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/models/__init__.py b/langfuse/api/resources/models/__init__.py deleted file mode 100644 index a41fff3e5..000000000 --- a/langfuse/api/resources/models/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import CreateModelRequest, PaginatedModels - -__all__ = ["CreateModelRequest", "PaginatedModels"] diff --git a/langfuse/api/resources/models/client.py b/langfuse/api/resources/models/client.py deleted file mode 100644 index 4f4b727fa..000000000 --- a/langfuse/api/resources/models/client.py +++ /dev/null @@ -1,607 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.model import Model -from .types.create_model_request import CreateModelRequest -from .types.paginated_models import PaginatedModels - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class ModelsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def create( - self, - *, - request: CreateModelRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> Model: - """ - Create a model - - Parameters - ---------- - request : CreateModelRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Model - - Examples - -------- - from langfuse import CreateModelRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.models.create( - request=CreateModelRequest( - model_name="modelName", - match_pattern="matchPattern", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/models", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Model, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedModels: - """ - Get all models - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedModels - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.models.list() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/models", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedModels, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> Model: - """ - Get a model - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Model - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.models.get( - id="id", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/models/{jsonable_encoder(id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Model, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> None: - """ - Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.models.delete( - id="id", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/models/{jsonable_encoder(id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncModelsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def create( - self, - *, - request: CreateModelRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> Model: - """ - Create a model - - Parameters - ---------- - request : CreateModelRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Model - - Examples - -------- - import asyncio - - from langfuse import CreateModelRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.models.create( - request=CreateModelRequest( - model_name="modelName", - match_pattern="matchPattern", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/models", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Model, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedModels: - """ - Get all models - - Parameters - ---------- - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedModels - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.models.list() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/models", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedModels, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> Model: - """ - Get a model - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Model - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.models.get( - id="id", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/models/{jsonable_encoder(id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Model, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete( - self, id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> None: - """ - Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. - - Parameters - ---------- - id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.models.delete( - id="id", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/models/{jsonable_encoder(id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/models/types/__init__.py b/langfuse/api/resources/models/types/__init__.py deleted file mode 100644 index 94285af35..000000000 --- a/langfuse/api/resources/models/types/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_model_request import CreateModelRequest -from .paginated_models import PaginatedModels - -__all__ = ["CreateModelRequest", "PaginatedModels"] diff --git a/langfuse/api/resources/models/types/paginated_models.py b/langfuse/api/resources/models/types/paginated_models.py deleted file mode 100644 index 3469a1fe6..000000000 --- a/langfuse/api/resources/models/types/paginated_models.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.model import Model -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class PaginatedModels(pydantic_v1.BaseModel): - data: typing.List[Model] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations/__init__.py b/langfuse/api/resources/observations/__init__.py deleted file mode 100644 index 95fd7c721..000000000 --- a/langfuse/api/resources/observations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import Observations, ObservationsViews - -__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/resources/observations/types/__init__.py b/langfuse/api/resources/observations/types/__init__.py deleted file mode 100644 index 60f9d4e01..000000000 --- a/langfuse/api/resources/observations/types/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .observations import Observations -from .observations_views import ObservationsViews - -__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/resources/observations/types/observations.py b/langfuse/api/resources/observations/types/observations.py deleted file mode 100644 index 1534dc87e..000000000 --- a/langfuse/api/resources/observations/types/observations.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.observation import Observation -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class Observations(pydantic_v1.BaseModel): - data: typing.List[Observation] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations/types/observations_views.py b/langfuse/api/resources/observations/types/observations_views.py deleted file mode 100644 index ed86b7d1e..000000000 --- a/langfuse/api/resources/observations/types/observations_views.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.observations_view import ObservationsView -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class ObservationsViews(pydantic_v1.BaseModel): - data: typing.List[ObservationsView] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations_v_2/__init__.py b/langfuse/api/resources/observations_v_2/__init__.py deleted file mode 100644 index a04697f31..000000000 --- a/langfuse/api/resources/observations_v_2/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ObservationsV2Meta, ObservationsV2Response - -__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/resources/observations_v_2/types/__init__.py b/langfuse/api/resources/observations_v_2/types/__init__.py deleted file mode 100644 index b62504c61..000000000 --- a/langfuse/api/resources/observations_v_2/types/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .observations_v_2_meta import ObservationsV2Meta -from .observations_v_2_response import ObservationsV2Response - -__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py b/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py deleted file mode 100644 index d720db59b..000000000 --- a/langfuse/api/resources/observations_v_2/types/observations_v_2_meta.py +++ /dev/null @@ -1,49 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ObservationsV2Meta(pydantic_v1.BaseModel): - """ - Metadata for cursor-based pagination - """ - - cursor: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Base64-encoded cursor to use for retrieving the next page. If not present, there are no more results. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py b/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py deleted file mode 100644 index fdea2c3c3..000000000 --- a/langfuse/api/resources/observations_v_2/types/observations_v_2_response.py +++ /dev/null @@ -1,55 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .observations_v_2_meta import ObservationsV2Meta - - -class ObservationsV2Response(pydantic_v1.BaseModel): - """ - Response containing observations with field-group-based filtering and cursor-based pagination. - - The `data` array contains observation objects with only the requested field groups included. - Use the `cursor` in `meta` to retrieve the next page of results. - """ - - data: typing.List[typing.Dict[str, typing.Any]] = pydantic_v1.Field() - """ - Array of observation objects. Fields included depend on the `fields` parameter in the request. - """ - - meta: ObservationsV2Meta - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/__init__.py b/langfuse/api/resources/opentelemetry/__init__.py deleted file mode 100644 index bada2052f..000000000 --- a/langfuse/api/resources/opentelemetry/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - OtelAttribute, - OtelAttributeValue, - OtelResource, - OtelResourceSpan, - OtelScope, - OtelScopeSpan, - OtelSpan, - OtelTraceResponse, -) - -__all__ = [ - "OtelAttribute", - "OtelAttributeValue", - "OtelResource", - "OtelResourceSpan", - "OtelScope", - "OtelScopeSpan", - "OtelSpan", - "OtelTraceResponse", -] diff --git a/langfuse/api/resources/opentelemetry/types/__init__.py b/langfuse/api/resources/opentelemetry/types/__init__.py deleted file mode 100644 index 4ca603db6..000000000 --- a/langfuse/api/resources/opentelemetry/types/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .otel_attribute import OtelAttribute -from .otel_attribute_value import OtelAttributeValue -from .otel_resource import OtelResource -from .otel_resource_span import OtelResourceSpan -from .otel_scope import OtelScope -from .otel_scope_span import OtelScopeSpan -from .otel_span import OtelSpan -from .otel_trace_response import OtelTraceResponse - -__all__ = [ - "OtelAttribute", - "OtelAttributeValue", - "OtelResource", - "OtelResourceSpan", - "OtelScope", - "OtelScopeSpan", - "OtelSpan", - "OtelTraceResponse", -] diff --git a/langfuse/api/resources/opentelemetry/types/otel_attribute.py b/langfuse/api/resources/opentelemetry/types/otel_attribute.py deleted file mode 100644 index 91b9e2b70..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_attribute.py +++ /dev/null @@ -1,55 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .otel_attribute_value import OtelAttributeValue - - -class OtelAttribute(pydantic_v1.BaseModel): - """ - Key-value attribute pair for resources, scopes, or spans - """ - - key: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Attribute key (e.g., "service.name", "langfuse.observation.type") - """ - - value: typing.Optional[OtelAttributeValue] = pydantic_v1.Field(default=None) - """ - Attribute value - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_attribute_value.py b/langfuse/api/resources/opentelemetry/types/otel_attribute_value.py deleted file mode 100644 index 51f026495..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_attribute_value.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OtelAttributeValue(pydantic_v1.BaseModel): - """ - Attribute value wrapper supporting different value types - """ - - string_value: typing.Optional[str] = pydantic_v1.Field( - alias="stringValue", default=None - ) - """ - String value - """ - - int_value: typing.Optional[int] = pydantic_v1.Field(alias="intValue", default=None) - """ - Integer value - """ - - double_value: typing.Optional[float] = pydantic_v1.Field( - alias="doubleValue", default=None - ) - """ - Double value - """ - - bool_value: typing.Optional[bool] = pydantic_v1.Field( - alias="boolValue", default=None - ) - """ - Boolean value - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_resource.py b/langfuse/api/resources/opentelemetry/types/otel_resource.py deleted file mode 100644 index 0d76d5a15..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_resource.py +++ /dev/null @@ -1,52 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .otel_attribute import OtelAttribute - - -class OtelResource(pydantic_v1.BaseModel): - """ - Resource attributes identifying the source of telemetry - """ - - attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic_v1.Field( - default=None - ) - """ - Resource attributes like service.name, service.version, etc. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_resource_span.py b/langfuse/api/resources/opentelemetry/types/otel_resource_span.py deleted file mode 100644 index e270ba7d8..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_resource_span.py +++ /dev/null @@ -1,60 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .otel_resource import OtelResource -from .otel_scope_span import OtelScopeSpan - - -class OtelResourceSpan(pydantic_v1.BaseModel): - """ - Represents a collection of spans from a single resource as per OTLP specification - """ - - resource: typing.Optional[OtelResource] = pydantic_v1.Field(default=None) - """ - Resource information - """ - - scope_spans: typing.Optional[typing.List[OtelScopeSpan]] = pydantic_v1.Field( - alias="scopeSpans", default=None - ) - """ - Array of scope spans - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_scope.py b/langfuse/api/resources/opentelemetry/types/otel_scope.py deleted file mode 100644 index 71e9b75b8..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_scope.py +++ /dev/null @@ -1,62 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .otel_attribute import OtelAttribute - - -class OtelScope(pydantic_v1.BaseModel): - """ - Instrumentation scope information - """ - - name: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Instrumentation scope name - """ - - version: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Instrumentation scope version - """ - - attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic_v1.Field( - default=None - ) - """ - Additional scope attributes - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_scope_span.py b/langfuse/api/resources/opentelemetry/types/otel_scope_span.py deleted file mode 100644 index 854951a60..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_scope_span.py +++ /dev/null @@ -1,56 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .otel_scope import OtelScope -from .otel_span import OtelSpan - - -class OtelScopeSpan(pydantic_v1.BaseModel): - """ - Collection of spans from a single instrumentation scope - """ - - scope: typing.Optional[OtelScope] = pydantic_v1.Field(default=None) - """ - Instrumentation scope information - """ - - spans: typing.Optional[typing.List[OtelSpan]] = pydantic_v1.Field(default=None) - """ - Array of spans - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_span.py b/langfuse/api/resources/opentelemetry/types/otel_span.py deleted file mode 100644 index 08b7be7fb..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_span.py +++ /dev/null @@ -1,104 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .otel_attribute import OtelAttribute - - -class OtelSpan(pydantic_v1.BaseModel): - """ - Individual span representing a unit of work or operation - """ - - trace_id: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="traceId", default=None - ) - """ - Trace ID (16 bytes, hex-encoded string in JSON or Buffer in binary) - """ - - span_id: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="spanId", default=None - ) - """ - Span ID (8 bytes, hex-encoded string in JSON or Buffer in binary) - """ - - parent_span_id: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="parentSpanId", default=None - ) - """ - Parent span ID if this is a child span - """ - - name: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Span name describing the operation - """ - - kind: typing.Optional[int] = pydantic_v1.Field(default=None) - """ - Span kind (1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER) - """ - - start_time_unix_nano: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="startTimeUnixNano", default=None - ) - """ - Start time in nanoseconds since Unix epoch - """ - - end_time_unix_nano: typing.Optional[typing.Any] = pydantic_v1.Field( - alias="endTimeUnixNano", default=None - ) - """ - End time in nanoseconds since Unix epoch - """ - - attributes: typing.Optional[typing.List[OtelAttribute]] = pydantic_v1.Field( - default=None - ) - """ - Span attributes including Langfuse-specific attributes (langfuse.observation.*) - """ - - status: typing.Optional[typing.Any] = pydantic_v1.Field(default=None) - """ - Span status object - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/opentelemetry/types/otel_trace_response.py b/langfuse/api/resources/opentelemetry/types/otel_trace_response.py deleted file mode 100644 index ef9897f06..000000000 --- a/langfuse/api/resources/opentelemetry/types/otel_trace_response.py +++ /dev/null @@ -1,44 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OtelTraceResponse(pydantic_v1.BaseModel): - """ - Response from trace export request. Empty object indicates success. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/__init__.py b/langfuse/api/resources/organizations/__init__.py deleted file mode 100644 index 36249ae36..000000000 --- a/langfuse/api/resources/organizations/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - DeleteMembershipRequest, - MembershipDeletionResponse, - MembershipRequest, - MembershipResponse, - MembershipRole, - MembershipsResponse, - OrganizationApiKey, - OrganizationApiKeysResponse, - OrganizationProject, - OrganizationProjectsResponse, -) - -__all__ = [ - "DeleteMembershipRequest", - "MembershipDeletionResponse", - "MembershipRequest", - "MembershipResponse", - "MembershipRole", - "MembershipsResponse", - "OrganizationApiKey", - "OrganizationApiKeysResponse", - "OrganizationProject", - "OrganizationProjectsResponse", -] diff --git a/langfuse/api/resources/organizations/client.py b/langfuse/api/resources/organizations/client.py deleted file mode 100644 index 1e7bcd117..000000000 --- a/langfuse/api/resources/organizations/client.py +++ /dev/null @@ -1,1205 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.delete_membership_request import DeleteMembershipRequest -from .types.membership_deletion_response import MembershipDeletionResponse -from .types.membership_request import MembershipRequest -from .types.membership_response import MembershipResponse -from .types.memberships_response import MembershipsResponse -from .types.organization_api_keys_response import OrganizationApiKeysResponse -from .types.organization_projects_response import OrganizationProjectsResponse - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class OrganizationsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get_organization_memberships( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> MembershipsResponse: - """ - Get all memberships for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipsResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.get_organization_memberships() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/organizations/memberships", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def update_organization_membership( - self, - *, - request: MembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipResponse: - """ - Create or update a membership for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request : MembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipResponse - - Examples - -------- - from langfuse import MembershipRequest, MembershipRole - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.update_organization_membership( - request=MembershipRequest( - user_id="userId", - role=MembershipRole.OWNER, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/organizations/memberships", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_organization_membership( - self, - *, - request: DeleteMembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipDeletionResponse: - """ - Delete a membership from the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request : DeleteMembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipDeletionResponse - - Examples - -------- - from langfuse import DeleteMembershipRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.delete_organization_membership( - request=DeleteMembershipRequest( - user_id="userId", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/organizations/memberships", - method="DELETE", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - MembershipDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_project_memberships( - self, - project_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipsResponse: - """ - Get all memberships for a specific project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipsResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.get_project_memberships( - project_id="projectId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/memberships", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def update_project_membership( - self, - project_id: str, - *, - request: MembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipResponse: - """ - Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. - - Parameters - ---------- - project_id : str - - request : MembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipResponse - - Examples - -------- - from langfuse import MembershipRequest, MembershipRole - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.update_project_membership( - project_id="projectId", - request=MembershipRequest( - user_id="userId", - role=MembershipRole.OWNER, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/memberships", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_project_membership( - self, - project_id: str, - *, - request: DeleteMembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipDeletionResponse: - """ - Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. - - Parameters - ---------- - project_id : str - - request : DeleteMembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipDeletionResponse - - Examples - -------- - from langfuse import DeleteMembershipRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.delete_project_membership( - project_id="projectId", - request=DeleteMembershipRequest( - user_id="userId", - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/memberships", - method="DELETE", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - MembershipDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_organization_projects( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> OrganizationProjectsResponse: - """ - Get all projects for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - OrganizationProjectsResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.get_organization_projects() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/organizations/projects", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - OrganizationProjectsResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_organization_api_keys( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> OrganizationApiKeysResponse: - """ - Get all API keys for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - OrganizationApiKeysResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.organizations.get_organization_api_keys() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/organizations/apiKeys", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - OrganizationApiKeysResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncOrganizationsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get_organization_memberships( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> MembershipsResponse: - """ - Get all memberships for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipsResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.get_organization_memberships() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/organizations/memberships", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def update_organization_membership( - self, - *, - request: MembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipResponse: - """ - Create or update a membership for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request : MembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipResponse - - Examples - -------- - import asyncio - - from langfuse import MembershipRequest, MembershipRole - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.update_organization_membership( - request=MembershipRequest( - user_id="userId", - role=MembershipRole.OWNER, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/organizations/memberships", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_organization_membership( - self, - *, - request: DeleteMembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipDeletionResponse: - """ - Delete a membership from the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request : DeleteMembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipDeletionResponse - - Examples - -------- - import asyncio - - from langfuse import DeleteMembershipRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.delete_organization_membership( - request=DeleteMembershipRequest( - user_id="userId", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/organizations/memberships", - method="DELETE", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - MembershipDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_project_memberships( - self, - project_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipsResponse: - """ - Get all memberships for a specific project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipsResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.get_project_memberships( - project_id="projectId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/memberships", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipsResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def update_project_membership( - self, - project_id: str, - *, - request: MembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipResponse: - """ - Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. - - Parameters - ---------- - project_id : str - - request : MembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipResponse - - Examples - -------- - import asyncio - - from langfuse import MembershipRequest, MembershipRole - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.update_project_membership( - project_id="projectId", - request=MembershipRequest( - user_id="userId", - role=MembershipRole.OWNER, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/memberships", - method="PUT", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(MembershipResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_project_membership( - self, - project_id: str, - *, - request: DeleteMembershipRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> MembershipDeletionResponse: - """ - Delete a membership from a specific project (requires organization-scoped API key). The user must be a member of the organization. - - Parameters - ---------- - project_id : str - - request : DeleteMembershipRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MembershipDeletionResponse - - Examples - -------- - import asyncio - - from langfuse import DeleteMembershipRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.delete_project_membership( - project_id="projectId", - request=DeleteMembershipRequest( - user_id="userId", - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/memberships", - method="DELETE", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - MembershipDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_organization_projects( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> OrganizationProjectsResponse: - """ - Get all projects for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - OrganizationProjectsResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.get_organization_projects() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/organizations/projects", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - OrganizationProjectsResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_organization_api_keys( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> OrganizationApiKeysResponse: - """ - Get all API keys for the organization associated with the API key (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - OrganizationApiKeysResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.organizations.get_organization_api_keys() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/organizations/apiKeys", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - OrganizationApiKeysResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/organizations/types/__init__.py b/langfuse/api/resources/organizations/types/__init__.py deleted file mode 100644 index b3ea09797..000000000 --- a/langfuse/api/resources/organizations/types/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .delete_membership_request import DeleteMembershipRequest -from .membership_deletion_response import MembershipDeletionResponse -from .membership_request import MembershipRequest -from .membership_response import MembershipResponse -from .membership_role import MembershipRole -from .memberships_response import MembershipsResponse -from .organization_api_key import OrganizationApiKey -from .organization_api_keys_response import OrganizationApiKeysResponse -from .organization_project import OrganizationProject -from .organization_projects_response import OrganizationProjectsResponse - -__all__ = [ - "DeleteMembershipRequest", - "MembershipDeletionResponse", - "MembershipRequest", - "MembershipResponse", - "MembershipRole", - "MembershipsResponse", - "OrganizationApiKey", - "OrganizationApiKeysResponse", - "OrganizationProject", - "OrganizationProjectsResponse", -] diff --git a/langfuse/api/resources/organizations/types/delete_membership_request.py b/langfuse/api/resources/organizations/types/delete_membership_request.py deleted file mode 100644 index 6752b0aae..000000000 --- a/langfuse/api/resources/organizations/types/delete_membership_request.py +++ /dev/null @@ -1,44 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DeleteMembershipRequest(pydantic_v1.BaseModel): - user_id: str = pydantic_v1.Field(alias="userId") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/membership_deletion_response.py b/langfuse/api/resources/organizations/types/membership_deletion_response.py deleted file mode 100644 index f9c1915b7..000000000 --- a/langfuse/api/resources/organizations/types/membership_deletion_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class MembershipDeletionResponse(pydantic_v1.BaseModel): - message: str - user_id: str = pydantic_v1.Field(alias="userId") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/membership_request.py b/langfuse/api/resources/organizations/types/membership_request.py deleted file mode 100644 index a7f046f51..000000000 --- a/langfuse/api/resources/organizations/types/membership_request.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .membership_role import MembershipRole - - -class MembershipRequest(pydantic_v1.BaseModel): - user_id: str = pydantic_v1.Field(alias="userId") - role: MembershipRole - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/membership_response.py b/langfuse/api/resources/organizations/types/membership_response.py deleted file mode 100644 index e9d82f3c7..000000000 --- a/langfuse/api/resources/organizations/types/membership_response.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .membership_role import MembershipRole - - -class MembershipResponse(pydantic_v1.BaseModel): - user_id: str = pydantic_v1.Field(alias="userId") - role: MembershipRole - email: str - name: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/memberships_response.py b/langfuse/api/resources/organizations/types/memberships_response.py deleted file mode 100644 index 0a8091449..000000000 --- a/langfuse/api/resources/organizations/types/memberships_response.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .membership_response import MembershipResponse - - -class MembershipsResponse(pydantic_v1.BaseModel): - memberships: typing.List[MembershipResponse] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/organization_api_key.py b/langfuse/api/resources/organizations/types/organization_api_key.py deleted file mode 100644 index ad54bb182..000000000 --- a/langfuse/api/resources/organizations/types/organization_api_key.py +++ /dev/null @@ -1,54 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OrganizationApiKey(pydantic_v1.BaseModel): - id: str - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - expires_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="expiresAt", default=None - ) - last_used_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="lastUsedAt", default=None - ) - note: typing.Optional[str] = None - public_key: str = pydantic_v1.Field(alias="publicKey") - display_secret_key: str = pydantic_v1.Field(alias="displaySecretKey") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/organization_api_keys_response.py b/langfuse/api/resources/organizations/types/organization_api_keys_response.py deleted file mode 100644 index e19ce6373..000000000 --- a/langfuse/api/resources/organizations/types/organization_api_keys_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .organization_api_key import OrganizationApiKey - - -class OrganizationApiKeysResponse(pydantic_v1.BaseModel): - api_keys: typing.List[OrganizationApiKey] = pydantic_v1.Field(alias="apiKeys") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/organization_project.py b/langfuse/api/resources/organizations/types/organization_project.py deleted file mode 100644 index 87f245b9a..000000000 --- a/langfuse/api/resources/organizations/types/organization_project.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class OrganizationProject(pydantic_v1.BaseModel): - id: str - name: str - metadata: typing.Optional[typing.Dict[str, typing.Any]] = None - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/organizations/types/organization_projects_response.py b/langfuse/api/resources/organizations/types/organization_projects_response.py deleted file mode 100644 index 1c939a3e0..000000000 --- a/langfuse/api/resources/organizations/types/organization_projects_response.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .organization_project import OrganizationProject - - -class OrganizationProjectsResponse(pydantic_v1.BaseModel): - projects: typing.List[OrganizationProject] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/__init__.py b/langfuse/api/resources/projects/__init__.py deleted file mode 100644 index 7161cfff1..000000000 --- a/langfuse/api/resources/projects/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - ApiKeyDeletionResponse, - ApiKeyList, - ApiKeyResponse, - ApiKeySummary, - Organization, - Project, - ProjectDeletionResponse, - Projects, -) - -__all__ = [ - "ApiKeyDeletionResponse", - "ApiKeyList", - "ApiKeyResponse", - "ApiKeySummary", - "Organization", - "Project", - "ProjectDeletionResponse", - "Projects", -] diff --git a/langfuse/api/resources/projects/client.py b/langfuse/api/resources/projects/client.py deleted file mode 100644 index 3db173d75..000000000 --- a/langfuse/api/resources/projects/client.py +++ /dev/null @@ -1,1110 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.api_key_deletion_response import ApiKeyDeletionResponse -from .types.api_key_list import ApiKeyList -from .types.api_key_response import ApiKeyResponse -from .types.project import Project -from .types.project_deletion_response import ProjectDeletionResponse -from .types.projects import Projects - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class ProjectsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> Projects: - """ - Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Projects - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.get() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/projects", method="GET", request_options=request_options - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Projects, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create( - self, - *, - name: str, - retention: int, - metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> Project: - """ - Create a new project (requires organization-scoped API key) - - Parameters - ---------- - name : str - - retention : int - Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. - - metadata : typing.Optional[typing.Dict[str, typing.Any]] - Optional metadata for the project - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Project - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.create( - name="name", - retention=1, - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/projects", - method="POST", - json={"name": name, "metadata": metadata, "retention": retention}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Project, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def update( - self, - project_id: str, - *, - name: str, - metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - retention: typing.Optional[int] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> Project: - """ - Update a project by ID (requires organization-scoped API key). - - Parameters - ---------- - project_id : str - - name : str - - metadata : typing.Optional[typing.Dict[str, typing.Any]] - Optional metadata for the project - - retention : typing.Optional[int] - Number of days to retain data. - Must be 0 or at least 3 days. - Requires data-retention entitlement for non-zero values. - Optional. Will retain existing retention setting if omitted. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Project - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.update( - project_id="projectId", - name="name", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}", - method="PUT", - json={"name": name, "metadata": metadata, "retention": retention}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Project, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete( - self, - project_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ProjectDeletionResponse: - """ - Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. - - Parameters - ---------- - project_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ProjectDeletionResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.delete( - project_id="projectId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - ProjectDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_api_keys( - self, - project_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ApiKeyList: - """ - Get all API keys for a project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ApiKeyList - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.get_api_keys( - project_id="projectId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ApiKeyList, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create_api_key( - self, - project_id: str, - *, - note: typing.Optional[str] = OMIT, - public_key: typing.Optional[str] = OMIT, - secret_key: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> ApiKeyResponse: - """ - Create a new API key for a project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - note : typing.Optional[str] - Optional note for the API key - - public_key : typing.Optional[str] - Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. - - secret_key : typing.Optional[str] - Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ApiKeyResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.create_api_key( - project_id="projectId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", - method="POST", - json={"note": note, "publicKey": public_key, "secretKey": secret_key}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ApiKeyResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_api_key( - self, - project_id: str, - api_key_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ApiKeyDeletionResponse: - """ - Delete an API key for a project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - api_key_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ApiKeyDeletionResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.projects.delete_api_key( - project_id="projectId", - api_key_id="apiKeyId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys/{jsonable_encoder(api_key_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - ApiKeyDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncProjectsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> Projects: - """ - Get Project associated with API key (requires project-scoped API key). You can use GET /api/public/organizations/projects to get all projects with an organization-scoped key. - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Projects - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.get() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/projects", method="GET", request_options=request_options - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Projects, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create( - self, - *, - name: str, - retention: int, - metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> Project: - """ - Create a new project (requires organization-scoped API key) - - Parameters - ---------- - name : str - - retention : int - Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. - - metadata : typing.Optional[typing.Dict[str, typing.Any]] - Optional metadata for the project - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Project - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.create( - name="name", - retention=1, - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/projects", - method="POST", - json={"name": name, "metadata": metadata, "retention": retention}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Project, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def update( - self, - project_id: str, - *, - name: str, - metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, - retention: typing.Optional[int] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> Project: - """ - Update a project by ID (requires organization-scoped API key). - - Parameters - ---------- - project_id : str - - name : str - - metadata : typing.Optional[typing.Dict[str, typing.Any]] - Optional metadata for the project - - retention : typing.Optional[int] - Number of days to retain data. - Must be 0 or at least 3 days. - Requires data-retention entitlement for non-zero values. - Optional. Will retain existing retention setting if omitted. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Project - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.update( - project_id="projectId", - name="name", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}", - method="PUT", - json={"name": name, "metadata": metadata, "retention": retention}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Project, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete( - self, - project_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ProjectDeletionResponse: - """ - Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. - - Parameters - ---------- - project_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ProjectDeletionResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.delete( - project_id="projectId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - ProjectDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_api_keys( - self, - project_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ApiKeyList: - """ - Get all API keys for a project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ApiKeyList - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.get_api_keys( - project_id="projectId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ApiKeyList, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create_api_key( - self, - project_id: str, - *, - note: typing.Optional[str] = OMIT, - public_key: typing.Optional[str] = OMIT, - secret_key: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> ApiKeyResponse: - """ - Create a new API key for a project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - note : typing.Optional[str] - Optional note for the API key - - public_key : typing.Optional[str] - Optional predefined public key. Must start with 'pk-lf-'. If provided, secretKey must also be provided. - - secret_key : typing.Optional[str] - Optional predefined secret key. Must start with 'sk-lf-'. If provided, publicKey must also be provided. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ApiKeyResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.create_api_key( - project_id="projectId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys", - method="POST", - json={"note": note, "publicKey": public_key, "secretKey": secret_key}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ApiKeyResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_api_key( - self, - project_id: str, - api_key_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ApiKeyDeletionResponse: - """ - Delete an API key for a project (requires organization-scoped API key) - - Parameters - ---------- - project_id : str - - api_key_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ApiKeyDeletionResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.projects.delete_api_key( - project_id="projectId", - api_key_id="apiKeyId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/projects/{jsonable_encoder(project_id)}/apiKeys/{jsonable_encoder(api_key_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - ApiKeyDeletionResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/projects/types/__init__.py b/langfuse/api/resources/projects/types/__init__.py deleted file mode 100644 index 6fd44aa52..000000000 --- a/langfuse/api/resources/projects/types/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .api_key_deletion_response import ApiKeyDeletionResponse -from .api_key_list import ApiKeyList -from .api_key_response import ApiKeyResponse -from .api_key_summary import ApiKeySummary -from .organization import Organization -from .project import Project -from .project_deletion_response import ProjectDeletionResponse -from .projects import Projects - -__all__ = [ - "ApiKeyDeletionResponse", - "ApiKeyList", - "ApiKeyResponse", - "ApiKeySummary", - "Organization", - "Project", - "ProjectDeletionResponse", - "Projects", -] diff --git a/langfuse/api/resources/projects/types/api_key_deletion_response.py b/langfuse/api/resources/projects/types/api_key_deletion_response.py deleted file mode 100644 index 6084400de..000000000 --- a/langfuse/api/resources/projects/types/api_key_deletion_response.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ApiKeyDeletionResponse(pydantic_v1.BaseModel): - """ - Response for API key deletion - """ - - success: bool - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/api_key_list.py b/langfuse/api/resources/projects/types/api_key_list.py deleted file mode 100644 index 0a798ddbf..000000000 --- a/langfuse/api/resources/projects/types/api_key_list.py +++ /dev/null @@ -1,49 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .api_key_summary import ApiKeySummary - - -class ApiKeyList(pydantic_v1.BaseModel): - """ - List of API keys for a project - """ - - api_keys: typing.List[ApiKeySummary] = pydantic_v1.Field(alias="apiKeys") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/api_key_response.py b/langfuse/api/resources/projects/types/api_key_response.py deleted file mode 100644 index fc9364faf..000000000 --- a/langfuse/api/resources/projects/types/api_key_response.py +++ /dev/null @@ -1,53 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ApiKeyResponse(pydantic_v1.BaseModel): - """ - Response for API key creation - """ - - id: str - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - public_key: str = pydantic_v1.Field(alias="publicKey") - secret_key: str = pydantic_v1.Field(alias="secretKey") - display_secret_key: str = pydantic_v1.Field(alias="displaySecretKey") - note: typing.Optional[str] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/api_key_summary.py b/langfuse/api/resources/projects/types/api_key_summary.py deleted file mode 100644 index b95633731..000000000 --- a/langfuse/api/resources/projects/types/api_key_summary.py +++ /dev/null @@ -1,58 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ApiKeySummary(pydantic_v1.BaseModel): - """ - Summary of an API key - """ - - id: str - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - expires_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="expiresAt", default=None - ) - last_used_at: typing.Optional[dt.datetime] = pydantic_v1.Field( - alias="lastUsedAt", default=None - ) - note: typing.Optional[str] = None - public_key: str = pydantic_v1.Field(alias="publicKey") - display_secret_key: str = pydantic_v1.Field(alias="displaySecretKey") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/organization.py b/langfuse/api/resources/projects/types/organization.py deleted file mode 100644 index 1a46b6f6c..000000000 --- a/langfuse/api/resources/projects/types/organization.py +++ /dev/null @@ -1,50 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class Organization(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - The unique identifier of the organization - """ - - name: str = pydantic_v1.Field() - """ - The name of the organization - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/project.py b/langfuse/api/resources/projects/types/project.py deleted file mode 100644 index 913dbb040..000000000 --- a/langfuse/api/resources/projects/types/project.py +++ /dev/null @@ -1,62 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .organization import Organization - - -class Project(pydantic_v1.BaseModel): - id: str - name: str - organization: Organization = pydantic_v1.Field() - """ - The organization this project belongs to - """ - - metadata: typing.Dict[str, typing.Any] = pydantic_v1.Field() - """ - Metadata for the project - """ - - retention_days: typing.Optional[int] = pydantic_v1.Field( - alias="retentionDays", default=None - ) - """ - Number of days to retain data. Null or 0 means no retention. Omitted if no retention is configured. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/project_deletion_response.py b/langfuse/api/resources/projects/types/project_deletion_response.py deleted file mode 100644 index 62c05d3d8..000000000 --- a/langfuse/api/resources/projects/types/project_deletion_response.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ProjectDeletionResponse(pydantic_v1.BaseModel): - success: bool - message: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/projects/types/projects.py b/langfuse/api/resources/projects/types/projects.py deleted file mode 100644 index c5eaabfbd..000000000 --- a/langfuse/api/resources/projects/types/projects.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .project import Project - - -class Projects(pydantic_v1.BaseModel): - data: typing.List[Project] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompt_version/client.py b/langfuse/api/resources/prompt_version/client.py deleted file mode 100644 index 5387c6e25..000000000 --- a/langfuse/api/resources/prompt_version/client.py +++ /dev/null @@ -1,199 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..prompts.types.prompt import Prompt - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class PromptVersionClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def update( - self, - name: str, - version: int, - *, - new_labels: typing.Sequence[str], - request_options: typing.Optional[RequestOptions] = None, - ) -> Prompt: - """ - Update labels for a specific prompt version - - Parameters - ---------- - name : str - The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), - the folder path must be URL encoded. - - version : int - Version of the prompt to update - - new_labels : typing.Sequence[str] - New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Prompt - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.prompt_version.update( - name="name", - version=1, - new_labels=["newLabels", "newLabels"], - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/v2/prompts/{jsonable_encoder(name)}/versions/{jsonable_encoder(version)}", - method="PATCH", - json={"newLabels": new_labels}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Prompt, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncPromptVersionClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def update( - self, - name: str, - version: int, - *, - new_labels: typing.Sequence[str], - request_options: typing.Optional[RequestOptions] = None, - ) -> Prompt: - """ - Update labels for a specific prompt version - - Parameters - ---------- - name : str - The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), - the folder path must be URL encoded. - - version : int - Version of the prompt to update - - new_labels : typing.Sequence[str] - New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Prompt - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.prompt_version.update( - name="name", - version=1, - new_labels=["newLabels", "newLabels"], - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/v2/prompts/{jsonable_encoder(name)}/versions/{jsonable_encoder(version)}", - method="PATCH", - json={"newLabels": new_labels}, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Prompt, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/prompts/__init__.py b/langfuse/api/resources/prompts/__init__.py deleted file mode 100644 index ea2f2f56a..000000000 --- a/langfuse/api/resources/prompts/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - BasePrompt, - ChatMessage, - ChatMessageWithPlaceholders, - ChatMessageWithPlaceholders_Chatmessage, - ChatMessageWithPlaceholders_Placeholder, - ChatPrompt, - CreateChatPromptRequest, - CreatePromptRequest, - CreatePromptRequest_Chat, - CreatePromptRequest_Text, - CreateTextPromptRequest, - PlaceholderMessage, - Prompt, - PromptMeta, - PromptMetaListResponse, - PromptType, - Prompt_Chat, - Prompt_Text, - TextPrompt, -) - -__all__ = [ - "BasePrompt", - "ChatMessage", - "ChatMessageWithPlaceholders", - "ChatMessageWithPlaceholders_Chatmessage", - "ChatMessageWithPlaceholders_Placeholder", - "ChatPrompt", - "CreateChatPromptRequest", - "CreatePromptRequest", - "CreatePromptRequest_Chat", - "CreatePromptRequest_Text", - "CreateTextPromptRequest", - "PlaceholderMessage", - "Prompt", - "PromptMeta", - "PromptMetaListResponse", - "PromptType", - "Prompt_Chat", - "Prompt_Text", - "TextPrompt", -] diff --git a/langfuse/api/resources/prompts/client.py b/langfuse/api/resources/prompts/client.py deleted file mode 100644 index b8d6f31d4..000000000 --- a/langfuse/api/resources/prompts/client.py +++ /dev/null @@ -1,749 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.create_prompt_request import CreatePromptRequest -from .types.prompt import Prompt -from .types.prompt_meta_list_response import PromptMetaListResponse - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class PromptsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get( - self, - prompt_name: str, - *, - version: typing.Optional[int] = None, - label: typing.Optional[str] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> Prompt: - """ - Get a prompt - - Parameters - ---------- - prompt_name : str - The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), - the folder path must be URL encoded. - - version : typing.Optional[int] - Version of the prompt to be retrieved. - - label : typing.Optional[str] - Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Prompt - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.prompts.get( - prompt_name="promptName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", - method="GET", - params={"version": version, "label": label}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Prompt, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def list( - self, - *, - name: typing.Optional[str] = None, - label: typing.Optional[str] = None, - tag: typing.Optional[str] = None, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - from_updated_at: typing.Optional[dt.datetime] = None, - to_updated_at: typing.Optional[dt.datetime] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PromptMetaListResponse: - """ - Get a list of prompt names with versions and labels - - Parameters - ---------- - name : typing.Optional[str] - - label : typing.Optional[str] - - tag : typing.Optional[str] - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - from_updated_at : typing.Optional[dt.datetime] - Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) - - to_updated_at : typing.Optional[dt.datetime] - Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PromptMetaListResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.prompts.list() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/prompts", - method="GET", - params={ - "name": name, - "label": label, - "tag": tag, - "page": page, - "limit": limit, - "fromUpdatedAt": serialize_datetime(from_updated_at) - if from_updated_at is not None - else None, - "toUpdatedAt": serialize_datetime(to_updated_at) - if to_updated_at is not None - else None, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PromptMetaListResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create( - self, - *, - request: CreatePromptRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> Prompt: - """ - Create a new version for the prompt with the given `name` - - Parameters - ---------- - request : CreatePromptRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Prompt - - Examples - -------- - from langfuse import ( - ChatMessageWithPlaceholders_Chatmessage, - CreatePromptRequest_Chat, - ) - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.prompts.create( - request=CreatePromptRequest_Chat( - name="name", - prompt=[ - ChatMessageWithPlaceholders_Chatmessage( - role="role", - content="content", - ), - ChatMessageWithPlaceholders_Chatmessage( - role="role", - content="content", - ), - ], - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/prompts", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Prompt, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete( - self, - prompt_name: str, - *, - label: typing.Optional[str] = None, - version: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> None: - """ - Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. - - Parameters - ---------- - prompt_name : str - The name of the prompt - - label : typing.Optional[str] - Optional label to filter deletion. If specified, deletes all prompt versions that have this label. - - version : typing.Optional[int] - Optional version to filter deletion. If specified, deletes only this specific version of the prompt. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.prompts.delete( - prompt_name="promptName", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", - method="DELETE", - params={"label": label, "version": version}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncPromptsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get( - self, - prompt_name: str, - *, - version: typing.Optional[int] = None, - label: typing.Optional[str] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> Prompt: - """ - Get a prompt - - Parameters - ---------- - prompt_name : str - The name of the prompt. If the prompt is in a folder (e.g., "folder/subfolder/prompt-name"), - the folder path must be URL encoded. - - version : typing.Optional[int] - Version of the prompt to be retrieved. - - label : typing.Optional[str] - Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Prompt - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.prompts.get( - prompt_name="promptName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", - method="GET", - params={"version": version, "label": label}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Prompt, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def list( - self, - *, - name: typing.Optional[str] = None, - label: typing.Optional[str] = None, - tag: typing.Optional[str] = None, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - from_updated_at: typing.Optional[dt.datetime] = None, - to_updated_at: typing.Optional[dt.datetime] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PromptMetaListResponse: - """ - Get a list of prompt names with versions and labels - - Parameters - ---------- - name : typing.Optional[str] - - label : typing.Optional[str] - - tag : typing.Optional[str] - - page : typing.Optional[int] - page number, starts at 1 - - limit : typing.Optional[int] - limit of items per page - - from_updated_at : typing.Optional[dt.datetime] - Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601) - - to_updated_at : typing.Optional[dt.datetime] - Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601) - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PromptMetaListResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.prompts.list() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/prompts", - method="GET", - params={ - "name": name, - "label": label, - "tag": tag, - "page": page, - "limit": limit, - "fromUpdatedAt": serialize_datetime(from_updated_at) - if from_updated_at is not None - else None, - "toUpdatedAt": serialize_datetime(to_updated_at) - if to_updated_at is not None - else None, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as( - PromptMetaListResponse, _response.json() - ) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create( - self, - *, - request: CreatePromptRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> Prompt: - """ - Create a new version for the prompt with the given `name` - - Parameters - ---------- - request : CreatePromptRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - Prompt - - Examples - -------- - import asyncio - - from langfuse import ( - ChatMessageWithPlaceholders_Chatmessage, - CreatePromptRequest_Chat, - ) - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.prompts.create( - request=CreatePromptRequest_Chat( - name="name", - prompt=[ - ChatMessageWithPlaceholders_Chatmessage( - role="role", - content="content", - ), - ChatMessageWithPlaceholders_Chatmessage( - role="role", - content="content", - ), - ], - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/prompts", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Prompt, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete( - self, - prompt_name: str, - *, - label: typing.Optional[str] = None, - version: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> None: - """ - Delete prompt versions. If neither version nor label is specified, all versions of the prompt are deleted. - - Parameters - ---------- - prompt_name : str - The name of the prompt - - label : typing.Optional[str] - Optional label to filter deletion. If specified, deletes all prompt versions that have this label. - - version : typing.Optional[int] - Optional version to filter deletion. If specified, deletes only this specific version of the prompt. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.prompts.delete( - prompt_name="promptName", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/v2/prompts/{jsonable_encoder(prompt_name)}", - method="DELETE", - params={"label": label, "version": version}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/prompts/types/__init__.py b/langfuse/api/resources/prompts/types/__init__.py deleted file mode 100644 index 6678ec262..000000000 --- a/langfuse/api/resources/prompts/types/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .base_prompt import BasePrompt -from .chat_message import ChatMessage -from .chat_message_with_placeholders import ( - ChatMessageWithPlaceholders, - ChatMessageWithPlaceholders_Chatmessage, - ChatMessageWithPlaceholders_Placeholder, -) -from .chat_prompt import ChatPrompt -from .create_chat_prompt_request import CreateChatPromptRequest -from .create_prompt_request import ( - CreatePromptRequest, - CreatePromptRequest_Chat, - CreatePromptRequest_Text, -) -from .create_text_prompt_request import CreateTextPromptRequest -from .placeholder_message import PlaceholderMessage -from .prompt import Prompt, Prompt_Chat, Prompt_Text -from .prompt_meta import PromptMeta -from .prompt_meta_list_response import PromptMetaListResponse -from .prompt_type import PromptType -from .text_prompt import TextPrompt - -__all__ = [ - "BasePrompt", - "ChatMessage", - "ChatMessageWithPlaceholders", - "ChatMessageWithPlaceholders_Chatmessage", - "ChatMessageWithPlaceholders_Placeholder", - "ChatPrompt", - "CreateChatPromptRequest", - "CreatePromptRequest", - "CreatePromptRequest_Chat", - "CreatePromptRequest_Text", - "CreateTextPromptRequest", - "PlaceholderMessage", - "Prompt", - "PromptMeta", - "PromptMetaListResponse", - "PromptType", - "Prompt_Chat", - "Prompt_Text", - "TextPrompt", -] diff --git a/langfuse/api/resources/prompts/types/base_prompt.py b/langfuse/api/resources/prompts/types/base_prompt.py deleted file mode 100644 index eff295cc5..000000000 --- a/langfuse/api/resources/prompts/types/base_prompt.py +++ /dev/null @@ -1,69 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class BasePrompt(pydantic_v1.BaseModel): - name: str - version: int - config: typing.Any - labels: typing.List[str] = pydantic_v1.Field() - """ - List of deployment labels of this prompt version. - """ - - tags: typing.List[str] = pydantic_v1.Field() - """ - List of tags. Used to filter via UI and API. The same across versions of a prompt. - """ - - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - """ - Commit message for this prompt version. - """ - - resolution_graph: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( - alias="resolutionGraph", default=None - ) - """ - The dependency resolution graph for the current prompt. Null if prompt has no dependencies. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/chat_message.py b/langfuse/api/resources/prompts/types/chat_message.py deleted file mode 100644 index d009bc8cf..000000000 --- a/langfuse/api/resources/prompts/types/chat_message.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ChatMessage(pydantic_v1.BaseModel): - role: str - content: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/chat_message_with_placeholders.py b/langfuse/api/resources/prompts/types/chat_message_with_placeholders.py deleted file mode 100644 index dc12d5073..000000000 --- a/langfuse/api/resources/prompts/types/chat_message_with_placeholders.py +++ /dev/null @@ -1,87 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ChatMessageWithPlaceholders_Chatmessage(pydantic_v1.BaseModel): - role: str - content: str - type: typing.Literal["chatmessage"] = "chatmessage" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class ChatMessageWithPlaceholders_Placeholder(pydantic_v1.BaseModel): - name: str - type: typing.Literal["placeholder"] = "placeholder" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -ChatMessageWithPlaceholders = typing.Union[ - ChatMessageWithPlaceholders_Chatmessage, ChatMessageWithPlaceholders_Placeholder -] diff --git a/langfuse/api/resources/prompts/types/chat_prompt.py b/langfuse/api/resources/prompts/types/chat_prompt.py deleted file mode 100644 index 494449ea2..000000000 --- a/langfuse/api/resources/prompts/types/chat_prompt.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_prompt import BasePrompt -from .chat_message_with_placeholders import ChatMessageWithPlaceholders - - -class ChatPrompt(BasePrompt): - prompt: typing.List[ChatMessageWithPlaceholders] - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/create_chat_prompt_request.py b/langfuse/api/resources/prompts/types/create_chat_prompt_request.py deleted file mode 100644 index 1442164a6..000000000 --- a/langfuse/api/resources/prompts/types/create_chat_prompt_request.py +++ /dev/null @@ -1,63 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .chat_message_with_placeholders import ChatMessageWithPlaceholders - - -class CreateChatPromptRequest(pydantic_v1.BaseModel): - name: str - prompt: typing.List[ChatMessageWithPlaceholders] - config: typing.Optional[typing.Any] = None - labels: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - List of deployment labels of this prompt version. - """ - - tags: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - List of tags to apply to all versions of this prompt. - """ - - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - """ - Commit message for this prompt version. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/create_prompt_request.py b/langfuse/api/resources/prompts/types/create_prompt_request.py deleted file mode 100644 index b9518a7c4..000000000 --- a/langfuse/api/resources/prompts/types/create_prompt_request.py +++ /dev/null @@ -1,103 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .chat_message_with_placeholders import ChatMessageWithPlaceholders - - -class CreatePromptRequest_Chat(pydantic_v1.BaseModel): - name: str - prompt: typing.List[ChatMessageWithPlaceholders] - config: typing.Optional[typing.Any] = None - labels: typing.Optional[typing.List[str]] = None - tags: typing.Optional[typing.List[str]] = None - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - type: typing.Literal["chat"] = "chat" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class CreatePromptRequest_Text(pydantic_v1.BaseModel): - name: str - prompt: str - config: typing.Optional[typing.Any] = None - labels: typing.Optional[typing.List[str]] = None - tags: typing.Optional[typing.List[str]] = None - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - type: typing.Literal["text"] = "text" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -CreatePromptRequest = typing.Union[CreatePromptRequest_Chat, CreatePromptRequest_Text] diff --git a/langfuse/api/resources/prompts/types/create_text_prompt_request.py b/langfuse/api/resources/prompts/types/create_text_prompt_request.py deleted file mode 100644 index d35fbb24d..000000000 --- a/langfuse/api/resources/prompts/types/create_text_prompt_request.py +++ /dev/null @@ -1,62 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateTextPromptRequest(pydantic_v1.BaseModel): - name: str - prompt: str - config: typing.Optional[typing.Any] = None - labels: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - List of deployment labels of this prompt version. - """ - - tags: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - List of tags to apply to all versions of this prompt. - """ - - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - """ - Commit message for this prompt version. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/placeholder_message.py b/langfuse/api/resources/prompts/types/placeholder_message.py deleted file mode 100644 index a3352b391..000000000 --- a/langfuse/api/resources/prompts/types/placeholder_message.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class PlaceholderMessage(pydantic_v1.BaseModel): - name: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/prompt.py b/langfuse/api/resources/prompts/types/prompt.py deleted file mode 100644 index 1ad894879..000000000 --- a/langfuse/api/resources/prompts/types/prompt.py +++ /dev/null @@ -1,111 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .chat_message_with_placeholders import ChatMessageWithPlaceholders - - -class Prompt_Chat(pydantic_v1.BaseModel): - prompt: typing.List[ChatMessageWithPlaceholders] - name: str - version: int - config: typing.Any - labels: typing.List[str] - tags: typing.List[str] - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - resolution_graph: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( - alias="resolutionGraph", default=None - ) - type: typing.Literal["chat"] = "chat" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class Prompt_Text(pydantic_v1.BaseModel): - prompt: str - name: str - version: int - config: typing.Any - labels: typing.List[str] - tags: typing.List[str] - commit_message: typing.Optional[str] = pydantic_v1.Field( - alias="commitMessage", default=None - ) - resolution_graph: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field( - alias="resolutionGraph", default=None - ) - type: typing.Literal["text"] = "text" - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -Prompt = typing.Union[Prompt_Chat, Prompt_Text] diff --git a/langfuse/api/resources/prompts/types/prompt_meta.py b/langfuse/api/resources/prompts/types/prompt_meta.py deleted file mode 100644 index 35f8a06cf..000000000 --- a/langfuse/api/resources/prompts/types/prompt_meta.py +++ /dev/null @@ -1,58 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .prompt_type import PromptType - - -class PromptMeta(pydantic_v1.BaseModel): - name: str - type: PromptType = pydantic_v1.Field() - """ - Indicates whether the prompt is a text or chat prompt. - """ - - versions: typing.List[int] - labels: typing.List[str] - tags: typing.List[str] - last_updated_at: dt.datetime = pydantic_v1.Field(alias="lastUpdatedAt") - last_config: typing.Any = pydantic_v1.Field(alias="lastConfig") - """ - Config object of the most recent prompt version that matches the filters (if any are provided) - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/prompt_meta_list_response.py b/langfuse/api/resources/prompts/types/prompt_meta_list_response.py deleted file mode 100644 index d3dccf650..000000000 --- a/langfuse/api/resources/prompts/types/prompt_meta_list_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...utils.resources.pagination.types.meta_response import MetaResponse -from .prompt_meta import PromptMeta - - -class PromptMetaListResponse(pydantic_v1.BaseModel): - data: typing.List[PromptMeta] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/prompts/types/text_prompt.py b/langfuse/api/resources/prompts/types/text_prompt.py deleted file mode 100644 index e149ea322..000000000 --- a/langfuse/api/resources/prompts/types/text_prompt.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .base_prompt import BasePrompt - - -class TextPrompt(BasePrompt): - prompt: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/__init__.py b/langfuse/api/resources/scim/__init__.py deleted file mode 100644 index 29655a8da..000000000 --- a/langfuse/api/resources/scim/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - AuthenticationScheme, - BulkConfig, - EmptyResponse, - FilterConfig, - ResourceMeta, - ResourceType, - ResourceTypesResponse, - SchemaExtension, - SchemaResource, - SchemasResponse, - ScimEmail, - ScimFeatureSupport, - ScimName, - ScimUser, - ScimUsersListResponse, - ServiceProviderConfig, - UserMeta, -) - -__all__ = [ - "AuthenticationScheme", - "BulkConfig", - "EmptyResponse", - "FilterConfig", - "ResourceMeta", - "ResourceType", - "ResourceTypesResponse", - "SchemaExtension", - "SchemaResource", - "SchemasResponse", - "ScimEmail", - "ScimFeatureSupport", - "ScimName", - "ScimUser", - "ScimUsersListResponse", - "ServiceProviderConfig", - "UserMeta", -] diff --git a/langfuse/api/resources/scim/client.py b/langfuse/api/resources/scim/client.py deleted file mode 100644 index 38523a4f9..000000000 --- a/langfuse/api/resources/scim/client.py +++ /dev/null @@ -1,1042 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.empty_response import EmptyResponse -from .types.resource_types_response import ResourceTypesResponse -from .types.schemas_response import SchemasResponse -from .types.scim_email import ScimEmail -from .types.scim_name import ScimName -from .types.scim_user import ScimUser -from .types.scim_users_list_response import ScimUsersListResponse -from .types.service_provider_config import ServiceProviderConfig - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class ScimClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def get_service_provider_config( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> ServiceProviderConfig: - """ - Get SCIM Service Provider Configuration (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ServiceProviderConfig - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.get_service_provider_config() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/scim/ServiceProviderConfig", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ServiceProviderConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_resource_types( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> ResourceTypesResponse: - """ - Get SCIM Resource Types (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ResourceTypesResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.get_resource_types() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/scim/ResourceTypes", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ResourceTypesResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_schemas( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> SchemasResponse: - """ - Get SCIM Schemas (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SchemasResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.get_schemas() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/scim/Schemas", method="GET", request_options=request_options - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(SchemasResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def list_users( - self, - *, - filter: typing.Optional[str] = None, - start_index: typing.Optional[int] = None, - count: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScimUsersListResponse: - """ - List users in the organization (requires organization-scoped API key) - - Parameters - ---------- - filter : typing.Optional[str] - Filter expression (e.g. userName eq "value") - - start_index : typing.Optional[int] - 1-based index of the first result to return (default 1) - - count : typing.Optional[int] - Maximum number of results to return (default 100) - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScimUsersListResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.list_users() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/scim/Users", - method="GET", - params={"filter": filter, "startIndex": start_index, "count": count}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScimUsersListResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def create_user( - self, - *, - user_name: str, - name: ScimName, - emails: typing.Optional[typing.Sequence[ScimEmail]] = OMIT, - active: typing.Optional[bool] = OMIT, - password: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScimUser: - """ - Create a new user in the organization (requires organization-scoped API key) - - Parameters - ---------- - user_name : str - User's email address (required) - - name : ScimName - User's name information - - emails : typing.Optional[typing.Sequence[ScimEmail]] - User's email addresses - - active : typing.Optional[bool] - Whether the user is active - - password : typing.Optional[str] - Initial password for the user - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScimUser - - Examples - -------- - from langfuse import ScimName - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.create_user( - user_name="userName", - name=ScimName(), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/scim/Users", - method="POST", - json={ - "userName": user_name, - "name": name, - "emails": emails, - "active": active, - "password": password, - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScimUser, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_user( - self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> ScimUser: - """ - Get a specific user by ID (requires organization-scoped API key) - - Parameters - ---------- - user_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScimUser - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.get_user( - user_id="userId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/scim/Users/{jsonable_encoder(user_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScimUser, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete_user( - self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> EmptyResponse: - """ - Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. - - Parameters - ---------- - user_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - EmptyResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.scim.delete_user( - user_id="userId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/scim/Users/{jsonable_encoder(user_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(EmptyResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncScimClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def get_service_provider_config( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> ServiceProviderConfig: - """ - Get SCIM Service Provider Configuration (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ServiceProviderConfig - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.get_service_provider_config() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/scim/ServiceProviderConfig", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ServiceProviderConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_resource_types( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> ResourceTypesResponse: - """ - Get SCIM Resource Types (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ResourceTypesResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.get_resource_types() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/scim/ResourceTypes", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ResourceTypesResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_schemas( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> SchemasResponse: - """ - Get SCIM Schemas (requires organization-scoped API key) - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SchemasResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.get_schemas() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/scim/Schemas", method="GET", request_options=request_options - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(SchemasResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def list_users( - self, - *, - filter: typing.Optional[str] = None, - start_index: typing.Optional[int] = None, - count: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScimUsersListResponse: - """ - List users in the organization (requires organization-scoped API key) - - Parameters - ---------- - filter : typing.Optional[str] - Filter expression (e.g. userName eq "value") - - start_index : typing.Optional[int] - 1-based index of the first result to return (default 1) - - count : typing.Optional[int] - Maximum number of results to return (default 100) - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScimUsersListResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.list_users() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/scim/Users", - method="GET", - params={"filter": filter, "startIndex": start_index, "count": count}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScimUsersListResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def create_user( - self, - *, - user_name: str, - name: ScimName, - emails: typing.Optional[typing.Sequence[ScimEmail]] = OMIT, - active: typing.Optional[bool] = OMIT, - password: typing.Optional[str] = OMIT, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScimUser: - """ - Create a new user in the organization (requires organization-scoped API key) - - Parameters - ---------- - user_name : str - User's email address (required) - - name : ScimName - User's name information - - emails : typing.Optional[typing.Sequence[ScimEmail]] - User's email addresses - - active : typing.Optional[bool] - Whether the user is active - - password : typing.Optional[str] - Initial password for the user - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScimUser - - Examples - -------- - import asyncio - - from langfuse import ScimName - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.create_user( - user_name="userName", - name=ScimName(), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/scim/Users", - method="POST", - json={ - "userName": user_name, - "name": name, - "emails": emails, - "active": active, - "password": password, - }, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScimUser, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_user( - self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> ScimUser: - """ - Get a specific user by ID (requires organization-scoped API key) - - Parameters - ---------- - user_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScimUser - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.get_user( - user_id="userId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/scim/Users/{jsonable_encoder(user_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScimUser, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete_user( - self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> EmptyResponse: - """ - Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. - - Parameters - ---------- - user_id : str - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - EmptyResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.scim.delete_user( - user_id="userId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/scim/Users/{jsonable_encoder(user_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(EmptyResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/scim/types/__init__.py b/langfuse/api/resources/scim/types/__init__.py deleted file mode 100644 index c0b60e8c2..000000000 --- a/langfuse/api/resources/scim/types/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .authentication_scheme import AuthenticationScheme -from .bulk_config import BulkConfig -from .empty_response import EmptyResponse -from .filter_config import FilterConfig -from .resource_meta import ResourceMeta -from .resource_type import ResourceType -from .resource_types_response import ResourceTypesResponse -from .schema_extension import SchemaExtension -from .schema_resource import SchemaResource -from .schemas_response import SchemasResponse -from .scim_email import ScimEmail -from .scim_feature_support import ScimFeatureSupport -from .scim_name import ScimName -from .scim_user import ScimUser -from .scim_users_list_response import ScimUsersListResponse -from .service_provider_config import ServiceProviderConfig -from .user_meta import UserMeta - -__all__ = [ - "AuthenticationScheme", - "BulkConfig", - "EmptyResponse", - "FilterConfig", - "ResourceMeta", - "ResourceType", - "ResourceTypesResponse", - "SchemaExtension", - "SchemaResource", - "SchemasResponse", - "ScimEmail", - "ScimFeatureSupport", - "ScimName", - "ScimUser", - "ScimUsersListResponse", - "ServiceProviderConfig", - "UserMeta", -] diff --git a/langfuse/api/resources/scim/types/authentication_scheme.py b/langfuse/api/resources/scim/types/authentication_scheme.py deleted file mode 100644 index 6d6526901..000000000 --- a/langfuse/api/resources/scim/types/authentication_scheme.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class AuthenticationScheme(pydantic_v1.BaseModel): - name: str - description: str - spec_uri: str = pydantic_v1.Field(alias="specUri") - type: str - primary: bool - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/bulk_config.py b/langfuse/api/resources/scim/types/bulk_config.py deleted file mode 100644 index 0b41af5cf..000000000 --- a/langfuse/api/resources/scim/types/bulk_config.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class BulkConfig(pydantic_v1.BaseModel): - supported: bool - max_operations: int = pydantic_v1.Field(alias="maxOperations") - max_payload_size: int = pydantic_v1.Field(alias="maxPayloadSize") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/empty_response.py b/langfuse/api/resources/scim/types/empty_response.py deleted file mode 100644 index 82105e8a3..000000000 --- a/langfuse/api/resources/scim/types/empty_response.py +++ /dev/null @@ -1,44 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class EmptyResponse(pydantic_v1.BaseModel): - """ - Empty response for 204 No Content responses - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/filter_config.py b/langfuse/api/resources/scim/types/filter_config.py deleted file mode 100644 index 2bd035867..000000000 --- a/langfuse/api/resources/scim/types/filter_config.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class FilterConfig(pydantic_v1.BaseModel): - supported: bool - max_results: int = pydantic_v1.Field(alias="maxResults") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/resource_meta.py b/langfuse/api/resources/scim/types/resource_meta.py deleted file mode 100644 index a61d14442..000000000 --- a/langfuse/api/resources/scim/types/resource_meta.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ResourceMeta(pydantic_v1.BaseModel): - resource_type: str = pydantic_v1.Field(alias="resourceType") - location: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/resource_type.py b/langfuse/api/resources/scim/types/resource_type.py deleted file mode 100644 index 264dc87cf..000000000 --- a/langfuse/api/resources/scim/types/resource_type.py +++ /dev/null @@ -1,55 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .resource_meta import ResourceMeta -from .schema_extension import SchemaExtension - - -class ResourceType(pydantic_v1.BaseModel): - schemas: typing.Optional[typing.List[str]] = None - id: str - name: str - endpoint: str - description: str - schema_: str = pydantic_v1.Field(alias="schema") - schema_extensions: typing.List[SchemaExtension] = pydantic_v1.Field( - alias="schemaExtensions" - ) - meta: ResourceMeta - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/resource_types_response.py b/langfuse/api/resources/scim/types/resource_types_response.py deleted file mode 100644 index cce65b8d1..000000000 --- a/langfuse/api/resources/scim/types/resource_types_response.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .resource_type import ResourceType - - -class ResourceTypesResponse(pydantic_v1.BaseModel): - schemas: typing.List[str] - total_results: int = pydantic_v1.Field(alias="totalResults") - resources: typing.List[ResourceType] = pydantic_v1.Field(alias="Resources") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/schema_extension.py b/langfuse/api/resources/scim/types/schema_extension.py deleted file mode 100644 index c5ede44b9..000000000 --- a/langfuse/api/resources/scim/types/schema_extension.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class SchemaExtension(pydantic_v1.BaseModel): - schema_: str = pydantic_v1.Field(alias="schema") - required: bool - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/schema_resource.py b/langfuse/api/resources/scim/types/schema_resource.py deleted file mode 100644 index e85cda9a0..000000000 --- a/langfuse/api/resources/scim/types/schema_resource.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .resource_meta import ResourceMeta - - -class SchemaResource(pydantic_v1.BaseModel): - id: str - name: str - description: str - attributes: typing.List[typing.Any] - meta: ResourceMeta - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/schemas_response.py b/langfuse/api/resources/scim/types/schemas_response.py deleted file mode 100644 index 4c7b8199a..000000000 --- a/langfuse/api/resources/scim/types/schemas_response.py +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .schema_resource import SchemaResource - - -class SchemasResponse(pydantic_v1.BaseModel): - schemas: typing.List[str] - total_results: int = pydantic_v1.Field(alias="totalResults") - resources: typing.List[SchemaResource] = pydantic_v1.Field(alias="Resources") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/scim_email.py b/langfuse/api/resources/scim/types/scim_email.py deleted file mode 100644 index 71b817809..000000000 --- a/langfuse/api/resources/scim/types/scim_email.py +++ /dev/null @@ -1,44 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ScimEmail(pydantic_v1.BaseModel): - primary: bool - value: str - type: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/scim_feature_support.py b/langfuse/api/resources/scim/types/scim_feature_support.py deleted file mode 100644 index 2aedc07b5..000000000 --- a/langfuse/api/resources/scim/types/scim_feature_support.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ScimFeatureSupport(pydantic_v1.BaseModel): - supported: bool - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/scim_name.py b/langfuse/api/resources/scim/types/scim_name.py deleted file mode 100644 index c2812a25a..000000000 --- a/langfuse/api/resources/scim/types/scim_name.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class ScimName(pydantic_v1.BaseModel): - formatted: typing.Optional[str] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/scim_user.py b/langfuse/api/resources/scim/types/scim_user.py deleted file mode 100644 index 581bab8c1..000000000 --- a/langfuse/api/resources/scim/types/scim_user.py +++ /dev/null @@ -1,52 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .scim_email import ScimEmail -from .scim_name import ScimName -from .user_meta import UserMeta - - -class ScimUser(pydantic_v1.BaseModel): - schemas: typing.List[str] - id: str - user_name: str = pydantic_v1.Field(alias="userName") - name: ScimName - emails: typing.List[ScimEmail] - meta: UserMeta - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/scim_users_list_response.py b/langfuse/api/resources/scim/types/scim_users_list_response.py deleted file mode 100644 index 3c41a4d16..000000000 --- a/langfuse/api/resources/scim/types/scim_users_list_response.py +++ /dev/null @@ -1,49 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .scim_user import ScimUser - - -class ScimUsersListResponse(pydantic_v1.BaseModel): - schemas: typing.List[str] - total_results: int = pydantic_v1.Field(alias="totalResults") - start_index: int = pydantic_v1.Field(alias="startIndex") - items_per_page: int = pydantic_v1.Field(alias="itemsPerPage") - resources: typing.List[ScimUser] = pydantic_v1.Field(alias="Resources") - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/service_provider_config.py b/langfuse/api/resources/scim/types/service_provider_config.py deleted file mode 100644 index 9bf611ae6..000000000 --- a/langfuse/api/resources/scim/types/service_provider_config.py +++ /dev/null @@ -1,60 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from .authentication_scheme import AuthenticationScheme -from .bulk_config import BulkConfig -from .filter_config import FilterConfig -from .resource_meta import ResourceMeta -from .scim_feature_support import ScimFeatureSupport - - -class ServiceProviderConfig(pydantic_v1.BaseModel): - schemas: typing.List[str] - documentation_uri: str = pydantic_v1.Field(alias="documentationUri") - patch: ScimFeatureSupport - bulk: BulkConfig - filter: FilterConfig - change_password: ScimFeatureSupport = pydantic_v1.Field(alias="changePassword") - sort: ScimFeatureSupport - etag: ScimFeatureSupport - authentication_schemes: typing.List[AuthenticationScheme] = pydantic_v1.Field( - alias="authenticationSchemes" - ) - meta: ResourceMeta - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/scim/types/user_meta.py b/langfuse/api/resources/scim/types/user_meta.py deleted file mode 100644 index 09cb7e6a0..000000000 --- a/langfuse/api/resources/scim/types/user_meta.py +++ /dev/null @@ -1,48 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class UserMeta(pydantic_v1.BaseModel): - resource_type: str = pydantic_v1.Field(alias="resourceType") - created: typing.Optional[str] = None - last_modified: typing.Optional[str] = pydantic_v1.Field( - alias="lastModified", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score/__init__.py b/langfuse/api/resources/score/__init__.py deleted file mode 100644 index 566310af3..000000000 --- a/langfuse/api/resources/score/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import CreateScoreRequest, CreateScoreResponse - -__all__ = ["CreateScoreRequest", "CreateScoreResponse"] diff --git a/langfuse/api/resources/score/client.py b/langfuse/api/resources/score/client.py deleted file mode 100644 index 0c259929f..000000000 --- a/langfuse/api/resources/score/client.py +++ /dev/null @@ -1,322 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from .types.create_score_request import CreateScoreRequest -from .types.create_score_response import CreateScoreResponse - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class ScoreClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def create( - self, - *, - request: CreateScoreRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> CreateScoreResponse: - """ - Create a score (supports both trace and session scores) - - Parameters - ---------- - request : CreateScoreRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CreateScoreResponse - - Examples - -------- - from langfuse import CreateScoreRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score.create( - request=CreateScoreRequest( - name="name", - value=1.1, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/scores", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(CreateScoreResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def delete( - self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> None: - """ - Delete a score (supports both trace and session scores) - - Parameters - ---------- - score_id : str - The unique langfuse identifier of a score - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score.delete( - score_id="scoreId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/scores/{jsonable_encoder(score_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncScoreClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def create( - self, - *, - request: CreateScoreRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> CreateScoreResponse: - """ - Create a score (supports both trace and session scores) - - Parameters - ---------- - request : CreateScoreRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - CreateScoreResponse - - Examples - -------- - import asyncio - - from langfuse import CreateScoreRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score.create( - request=CreateScoreRequest( - name="name", - value=1.1, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/scores", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(CreateScoreResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def delete( - self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> None: - """ - Delete a score (supports both trace and session scores) - - Parameters - ---------- - score_id : str - The unique langfuse identifier of a score - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score.delete( - score_id="scoreId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/scores/{jsonable_encoder(score_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/score/types/__init__.py b/langfuse/api/resources/score/types/__init__.py deleted file mode 100644 index 72d61f6f3..000000000 --- a/langfuse/api/resources/score/types/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_score_request import CreateScoreRequest -from .create_score_response import CreateScoreResponse - -__all__ = ["CreateScoreRequest", "CreateScoreResponse"] diff --git a/langfuse/api/resources/score/types/create_score_request.py b/langfuse/api/resources/score/types/create_score_request.py deleted file mode 100644 index 1f79f4a64..000000000 --- a/langfuse/api/resources/score/types/create_score_request.py +++ /dev/null @@ -1,97 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.create_score_value import CreateScoreValue -from ...commons.types.score_data_type import ScoreDataType - - -class CreateScoreRequest(pydantic_v1.BaseModel): - """ - Examples - -------- - from langfuse import CreateScoreRequest - - CreateScoreRequest( - name="novelty", - value=0.9, - trace_id="cdef-1234-5678-90ab", - ) - """ - - id: typing.Optional[str] = None - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - value: CreateScoreValue = pydantic_v1.Field() - """ - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) - """ - - comment: typing.Optional[str] = None - metadata: typing.Optional[typing.Dict[str, typing.Any]] = None - environment: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. - """ - - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - """ - The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. - """ - - data_type: typing.Optional[ScoreDataType] = pydantic_v1.Field( - alias="dataType", default=None - ) - """ - The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. - """ - - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - """ - Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score/types/create_score_response.py b/langfuse/api/resources/score/types/create_score_response.py deleted file mode 100644 index a8c90fce2..000000000 --- a/langfuse/api/resources/score/types/create_score_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class CreateScoreResponse(pydantic_v1.BaseModel): - id: str = pydantic_v1.Field() - """ - The id of the created object in Langfuse - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_configs/__init__.py b/langfuse/api/resources/score_configs/__init__.py deleted file mode 100644 index da401d35d..000000000 --- a/langfuse/api/resources/score_configs/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import CreateScoreConfigRequest, ScoreConfigs, UpdateScoreConfigRequest - -__all__ = ["CreateScoreConfigRequest", "ScoreConfigs", "UpdateScoreConfigRequest"] diff --git a/langfuse/api/resources/score_configs/client.py b/langfuse/api/resources/score_configs/client.py deleted file mode 100644 index 9ac68ccce..000000000 --- a/langfuse/api/resources/score_configs/client.py +++ /dev/null @@ -1,632 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.score_config import ScoreConfig -from .types.create_score_config_request import CreateScoreConfigRequest -from .types.score_configs import ScoreConfigs -from .types.update_score_config_request import UpdateScoreConfigRequest - -# this is used as the default value for optional parameters -OMIT = typing.cast(typing.Any, ...) - - -class ScoreConfigsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def create( - self, - *, - request: CreateScoreConfigRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScoreConfig: - """ - Create a score configuration (config). Score configs are used to define the structure of scores - - Parameters - ---------- - request : CreateScoreConfigRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfig - - Examples - -------- - from langfuse import CreateScoreConfigRequest, ScoreConfigDataType - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score_configs.create( - request=CreateScoreConfigRequest( - name="name", - data_type=ScoreConfigDataType.NUMERIC, - ), - ) - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/score-configs", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScoreConfigs: - """ - Get all score configs - - Parameters - ---------- - page : typing.Optional[int] - Page number, starts at 1. - - limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfigs - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score_configs.get() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/score-configs", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfigs, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get_by_id( - self, config_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> ScoreConfig: - """ - Get a score config - - Parameters - ---------- - config_id : str - The unique langfuse identifier of a score config - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfig - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score_configs.get_by_id( - config_id="configId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/score-configs/{jsonable_encoder(config_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def update( - self, - config_id: str, - *, - request: UpdateScoreConfigRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScoreConfig: - """ - Update a score config - - Parameters - ---------- - config_id : str - The unique langfuse identifier of a score config - - request : UpdateScoreConfigRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfig - - Examples - -------- - from langfuse import UpdateScoreConfigRequest - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score_configs.update( - config_id="configId", - request=UpdateScoreConfigRequest(), - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/score-configs/{jsonable_encoder(config_id)}", - method="PATCH", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncScoreConfigsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def create( - self, - *, - request: CreateScoreConfigRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScoreConfig: - """ - Create a score configuration (config). Score configs are used to define the structure of scores - - Parameters - ---------- - request : CreateScoreConfigRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfig - - Examples - -------- - import asyncio - - from langfuse import CreateScoreConfigRequest, ScoreConfigDataType - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score_configs.create( - request=CreateScoreConfigRequest( - name="name", - data_type=ScoreConfigDataType.NUMERIC, - ), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/score-configs", - method="POST", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScoreConfigs: - """ - Get all score configs - - Parameters - ---------- - page : typing.Optional[int] - Page number, starts at 1. - - limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfigs - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score_configs.get() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/score-configs", - method="GET", - params={"page": page, "limit": limit}, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfigs, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get_by_id( - self, config_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> ScoreConfig: - """ - Get a score config - - Parameters - ---------- - config_id : str - The unique langfuse identifier of a score config - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfig - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score_configs.get_by_id( - config_id="configId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/score-configs/{jsonable_encoder(config_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def update( - self, - config_id: str, - *, - request: UpdateScoreConfigRequest, - request_options: typing.Optional[RequestOptions] = None, - ) -> ScoreConfig: - """ - Update a score config - - Parameters - ---------- - config_id : str - The unique langfuse identifier of a score config - - request : UpdateScoreConfigRequest - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ScoreConfig - - Examples - -------- - import asyncio - - from langfuse import UpdateScoreConfigRequest - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score_configs.update( - config_id="configId", - request=UpdateScoreConfigRequest(), - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/score-configs/{jsonable_encoder(config_id)}", - method="PATCH", - json=request, - request_options=request_options, - omit=OMIT, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(ScoreConfig, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/score_configs/types/__init__.py b/langfuse/api/resources/score_configs/types/__init__.py deleted file mode 100644 index 1c328b614..000000000 --- a/langfuse/api/resources/score_configs/types/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .create_score_config_request import CreateScoreConfigRequest -from .score_configs import ScoreConfigs -from .update_score_config_request import UpdateScoreConfigRequest - -__all__ = ["CreateScoreConfigRequest", "ScoreConfigs", "UpdateScoreConfigRequest"] diff --git a/langfuse/api/resources/score_configs/types/create_score_config_request.py b/langfuse/api/resources/score_configs/types/create_score_config_request.py deleted file mode 100644 index eb5c5e325..000000000 --- a/langfuse/api/resources/score_configs/types/create_score_config_request.py +++ /dev/null @@ -1,72 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.config_category import ConfigCategory -from ...commons.types.score_config_data_type import ScoreConfigDataType - - -class CreateScoreConfigRequest(pydantic_v1.BaseModel): - name: str - data_type: ScoreConfigDataType = pydantic_v1.Field(alias="dataType") - categories: typing.Optional[typing.List[ConfigCategory]] = pydantic_v1.Field( - default=None - ) - """ - Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed - """ - - min_value: typing.Optional[float] = pydantic_v1.Field( - alias="minValue", default=None - ) - """ - Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ - """ - - max_value: typing.Optional[float] = pydantic_v1.Field( - alias="maxValue", default=None - ) - """ - Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ - """ - - description: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_configs/types/score_configs.py b/langfuse/api/resources/score_configs/types/score_configs.py deleted file mode 100644 index fc84e28a3..000000000 --- a/langfuse/api/resources/score_configs/types/score_configs.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.score_config import ScoreConfig -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class ScoreConfigs(pydantic_v1.BaseModel): - data: typing.List[ScoreConfig] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_configs/types/update_score_config_request.py b/langfuse/api/resources/score_configs/types/update_score_config_request.py deleted file mode 100644 index ce5f980b8..000000000 --- a/langfuse/api/resources/score_configs/types/update_score_config_request.py +++ /dev/null @@ -1,81 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.config_category import ConfigCategory - - -class UpdateScoreConfigRequest(pydantic_v1.BaseModel): - is_archived: typing.Optional[bool] = pydantic_v1.Field( - alias="isArchived", default=None - ) - """ - The status of the score config showing if it is archived or not - """ - - name: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The name of the score config - """ - - categories: typing.Optional[typing.List[ConfigCategory]] = pydantic_v1.Field( - default=None - ) - """ - Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed - """ - - min_value: typing.Optional[float] = pydantic_v1.Field( - alias="minValue", default=None - ) - """ - Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ - """ - - max_value: typing.Optional[float] = pydantic_v1.Field( - alias="maxValue", default=None - ) - """ - Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ - """ - - description: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/__init__.py b/langfuse/api/resources/score_v_2/__init__.py deleted file mode 100644 index 4e333a693..000000000 --- a/langfuse/api/resources/score_v_2/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import ( - GetScoresResponse, - GetScoresResponseData, - GetScoresResponseDataBoolean, - GetScoresResponseDataCategorical, - GetScoresResponseDataCorrection, - GetScoresResponseDataNumeric, - GetScoresResponseData_Boolean, - GetScoresResponseData_Categorical, - GetScoresResponseData_Correction, - GetScoresResponseData_Numeric, - GetScoresResponseTraceData, -) - -__all__ = [ - "GetScoresResponse", - "GetScoresResponseData", - "GetScoresResponseDataBoolean", - "GetScoresResponseDataCategorical", - "GetScoresResponseDataCorrection", - "GetScoresResponseDataNumeric", - "GetScoresResponseData_Boolean", - "GetScoresResponseData_Categorical", - "GetScoresResponseData_Correction", - "GetScoresResponseData_Numeric", - "GetScoresResponseTraceData", -] diff --git a/langfuse/api/resources/score_v_2/types/__init__.py b/langfuse/api/resources/score_v_2/types/__init__.py deleted file mode 100644 index d08e687ef..000000000 --- a/langfuse/api/resources/score_v_2/types/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .get_scores_response import GetScoresResponse -from .get_scores_response_data import ( - GetScoresResponseData, - GetScoresResponseData_Boolean, - GetScoresResponseData_Categorical, - GetScoresResponseData_Correction, - GetScoresResponseData_Numeric, -) -from .get_scores_response_data_boolean import GetScoresResponseDataBoolean -from .get_scores_response_data_categorical import GetScoresResponseDataCategorical -from .get_scores_response_data_correction import GetScoresResponseDataCorrection -from .get_scores_response_data_numeric import GetScoresResponseDataNumeric -from .get_scores_response_trace_data import GetScoresResponseTraceData - -__all__ = [ - "GetScoresResponse", - "GetScoresResponseData", - "GetScoresResponseDataBoolean", - "GetScoresResponseDataCategorical", - "GetScoresResponseDataCorrection", - "GetScoresResponseDataNumeric", - "GetScoresResponseData_Boolean", - "GetScoresResponseData_Categorical", - "GetScoresResponseData_Correction", - "GetScoresResponseData_Numeric", - "GetScoresResponseTraceData", -] diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response.py b/langfuse/api/resources/score_v_2/types/get_scores_response.py deleted file mode 100644 index 777bb799b..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...utils.resources.pagination.types.meta_response import MetaResponse -from .get_scores_response_data import GetScoresResponseData - - -class GetScoresResponse(pydantic_v1.BaseModel): - data: typing.List[GetScoresResponseData] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data.py deleted file mode 100644 index 4f73fbcae..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data.py +++ /dev/null @@ -1,282 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.score_source import ScoreSource -from .get_scores_response_trace_data import GetScoresResponseTraceData - - -class GetScoresResponseData_Numeric(pydantic_v1.BaseModel): - trace: typing.Optional[GetScoresResponseTraceData] = None - value: float - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field( - alias="dataType", default="NUMERIC" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class GetScoresResponseData_Categorical(pydantic_v1.BaseModel): - trace: typing.Optional[GetScoresResponseTraceData] = None - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field( - alias="dataType", default="CATEGORICAL" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class GetScoresResponseData_Boolean(pydantic_v1.BaseModel): - trace: typing.Optional[GetScoresResponseTraceData] = None - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field( - alias="dataType", default="BOOLEAN" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -class GetScoresResponseData_Correction(pydantic_v1.BaseModel): - trace: typing.Optional[GetScoresResponseTraceData] = None - value: float - string_value: str = pydantic_v1.Field(alias="stringValue") - id: str - trace_id: typing.Optional[str] = pydantic_v1.Field(alias="traceId", default=None) - session_id: typing.Optional[str] = pydantic_v1.Field( - alias="sessionId", default=None - ) - observation_id: typing.Optional[str] = pydantic_v1.Field( - alias="observationId", default=None - ) - dataset_run_id: typing.Optional[str] = pydantic_v1.Field( - alias="datasetRunId", default=None - ) - name: str - source: ScoreSource - timestamp: dt.datetime - created_at: dt.datetime = pydantic_v1.Field(alias="createdAt") - updated_at: dt.datetime = pydantic_v1.Field(alias="updatedAt") - author_user_id: typing.Optional[str] = pydantic_v1.Field( - alias="authorUserId", default=None - ) - comment: typing.Optional[str] = None - metadata: typing.Any - config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None) - queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None) - environment: str - data_type: typing.Literal["CORRECTION"] = pydantic_v1.Field( - alias="dataType", default="CORRECTION" - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} - - -GetScoresResponseData = typing.Union[ - GetScoresResponseData_Numeric, - GetScoresResponseData_Categorical, - GetScoresResponseData_Boolean, - GetScoresResponseData_Correction, -] diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data_boolean.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data_boolean.py deleted file mode 100644 index 48012990c..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data_boolean.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.boolean_score import BooleanScore -from .get_scores_response_trace_data import GetScoresResponseTraceData - - -class GetScoresResponseDataBoolean(BooleanScore): - trace: typing.Optional[GetScoresResponseTraceData] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data_categorical.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data_categorical.py deleted file mode 100644 index 6e27f6d64..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data_categorical.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.categorical_score import CategoricalScore -from .get_scores_response_trace_data import GetScoresResponseTraceData - - -class GetScoresResponseDataCategorical(CategoricalScore): - trace: typing.Optional[GetScoresResponseTraceData] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py deleted file mode 100644 index 0c59f29a8..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data_correction.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.correction_score import CorrectionScore -from .get_scores_response_trace_data import GetScoresResponseTraceData - - -class GetScoresResponseDataCorrection(CorrectionScore): - trace: typing.Optional[GetScoresResponseTraceData] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_data_numeric.py b/langfuse/api/resources/score_v_2/types/get_scores_response_data_numeric.py deleted file mode 100644 index f7342833f..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_data_numeric.py +++ /dev/null @@ -1,46 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.numeric_score import NumericScore -from .get_scores_response_trace_data import GetScoresResponseTraceData - - -class GetScoresResponseDataNumeric(NumericScore): - trace: typing.Optional[GetScoresResponseTraceData] = None - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/score_v_2/types/get_scores_response_trace_data.py b/langfuse/api/resources/score_v_2/types/get_scores_response_trace_data.py deleted file mode 100644 index 6e5539e35..000000000 --- a/langfuse/api/resources/score_v_2/types/get_scores_response_trace_data.py +++ /dev/null @@ -1,57 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class GetScoresResponseTraceData(pydantic_v1.BaseModel): - user_id: typing.Optional[str] = pydantic_v1.Field(alias="userId", default=None) - """ - The user ID associated with the trace referenced by score - """ - - tags: typing.Optional[typing.List[str]] = pydantic_v1.Field(default=None) - """ - A list of tags associated with the trace referenced by score - """ - - environment: typing.Optional[str] = pydantic_v1.Field(default=None) - """ - The environment of the trace referenced by score - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/sessions/__init__.py b/langfuse/api/resources/sessions/__init__.py deleted file mode 100644 index 048704297..000000000 --- a/langfuse/api/resources/sessions/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import PaginatedSessions - -__all__ = ["PaginatedSessions"] diff --git a/langfuse/api/resources/sessions/client.py b/langfuse/api/resources/sessions/client.py deleted file mode 100644 index d5ae779c3..000000000 --- a/langfuse/api/resources/sessions/client.py +++ /dev/null @@ -1,367 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.session_with_traces import SessionWithTraces -from .types.paginated_sessions import PaginatedSessions - - -class SessionsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - from_timestamp: typing.Optional[dt.datetime] = None, - to_timestamp: typing.Optional[dt.datetime] = None, - environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedSessions: - """ - Get sessions - - Parameters - ---------- - page : typing.Optional[int] - Page number, starts at 1 - - limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. - - from_timestamp : typing.Optional[dt.datetime] - Optional filter to only include sessions created on or after a certain datetime (ISO 8601) - - to_timestamp : typing.Optional[dt.datetime] - Optional filter to only include sessions created before a certain datetime (ISO 8601) - - environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] - Optional filter for sessions where the environment is one of the provided values. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedSessions - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.sessions.list() - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/sessions", - method="GET", - params={ - "page": page, - "limit": limit, - "fromTimestamp": serialize_datetime(from_timestamp) - if from_timestamp is not None - else None, - "toTimestamp": serialize_datetime(to_timestamp) - if to_timestamp is not None - else None, - "environment": environment, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedSessions, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - def get( - self, - session_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> SessionWithTraces: - """ - Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` - - Parameters - ---------- - session_id : str - The unique id of a session - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SessionWithTraces - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.sessions.get( - session_id="sessionId", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/sessions/{jsonable_encoder(session_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(SessionWithTraces, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - -class AsyncSessionsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def list( - self, - *, - page: typing.Optional[int] = None, - limit: typing.Optional[int] = None, - from_timestamp: typing.Optional[dt.datetime] = None, - to_timestamp: typing.Optional[dt.datetime] = None, - environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> PaginatedSessions: - """ - Get sessions - - Parameters - ---------- - page : typing.Optional[int] - Page number, starts at 1 - - limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. - - from_timestamp : typing.Optional[dt.datetime] - Optional filter to only include sessions created on or after a certain datetime (ISO 8601) - - to_timestamp : typing.Optional[dt.datetime] - Optional filter to only include sessions created before a certain datetime (ISO 8601) - - environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] - Optional filter for sessions where the environment is one of the provided values. - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - PaginatedSessions - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.sessions.list() - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/sessions", - method="GET", - params={ - "page": page, - "limit": limit, - "fromTimestamp": serialize_datetime(from_timestamp) - if from_timestamp is not None - else None, - "toTimestamp": serialize_datetime(to_timestamp) - if to_timestamp is not None - else None, - "environment": environment, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(PaginatedSessions, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - - async def get( - self, - session_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> SessionWithTraces: - """ - Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` - - Parameters - ---------- - session_id : str - The unique id of a session - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - SessionWithTraces - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.sessions.get( - session_id="sessionId", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/sessions/{jsonable_encoder(session_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(SessionWithTraces, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/langfuse/api/resources/sessions/types/__init__.py b/langfuse/api/resources/sessions/types/__init__.py deleted file mode 100644 index 42d63b428..000000000 --- a/langfuse/api/resources/sessions/types/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .paginated_sessions import PaginatedSessions - -__all__ = ["PaginatedSessions"] diff --git a/langfuse/api/resources/sessions/types/paginated_sessions.py b/langfuse/api/resources/sessions/types/paginated_sessions.py deleted file mode 100644 index 5dd9fb497..000000000 --- a/langfuse/api/resources/sessions/types/paginated_sessions.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.session import Session -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class PaginatedSessions(pydantic_v1.BaseModel): - data: typing.List[Session] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/trace/__init__.py b/langfuse/api/resources/trace/__init__.py deleted file mode 100644 index 17855e971..000000000 --- a/langfuse/api/resources/trace/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .types import DeleteTraceResponse, Sort, Traces - -__all__ = ["DeleteTraceResponse", "Sort", "Traces"] diff --git a/langfuse/api/resources/trace/types/__init__.py b/langfuse/api/resources/trace/types/__init__.py deleted file mode 100644 index 929a1e047..000000000 --- a/langfuse/api/resources/trace/types/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .delete_trace_response import DeleteTraceResponse -from .sort import Sort -from .traces import Traces - -__all__ = ["DeleteTraceResponse", "Sort", "Traces"] diff --git a/langfuse/api/resources/trace/types/delete_trace_response.py b/langfuse/api/resources/trace/types/delete_trace_response.py deleted file mode 100644 index 450c894e2..000000000 --- a/langfuse/api/resources/trace/types/delete_trace_response.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class DeleteTraceResponse(pydantic_v1.BaseModel): - message: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/trace/types/sort.py b/langfuse/api/resources/trace/types/sort.py deleted file mode 100644 index 76a5045b6..000000000 --- a/langfuse/api/resources/trace/types/sort.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class Sort(pydantic_v1.BaseModel): - id: str - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/trace/types/traces.py b/langfuse/api/resources/trace/types/traces.py deleted file mode 100644 index 09f58978f..000000000 --- a/langfuse/api/resources/trace/types/traces.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ....core.datetime_utils import serialize_datetime -from ....core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 -from ...commons.types.trace_with_details import TraceWithDetails -from ...utils.resources.pagination.types.meta_response import MetaResponse - - -class Traces(pydantic_v1.BaseModel): - data: typing.List[TraceWithDetails] - meta: MetaResponse - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/resources/utils/__init__.py b/langfuse/api/resources/utils/__init__.py deleted file mode 100644 index b4ac87b8a..000000000 --- a/langfuse/api/resources/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .resources import MetaResponse, pagination - -__all__ = ["MetaResponse", "pagination"] diff --git a/langfuse/api/resources/utils/resources/__init__.py b/langfuse/api/resources/utils/resources/__init__.py deleted file mode 100644 index 7e65ff270..000000000 --- a/langfuse/api/resources/utils/resources/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from . import pagination -from .pagination import MetaResponse - -__all__ = ["MetaResponse", "pagination"] diff --git a/langfuse/api/resources/utils/resources/pagination/types/__init__.py b/langfuse/api/resources/utils/resources/pagination/types/__init__.py deleted file mode 100644 index 79bb6018e..000000000 --- a/langfuse/api/resources/utils/resources/pagination/types/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .meta_response import MetaResponse - -__all__ = ["MetaResponse"] diff --git a/langfuse/api/resources/utils/resources/pagination/types/meta_response.py b/langfuse/api/resources/utils/resources/pagination/types/meta_response.py deleted file mode 100644 index 2d082c68f..000000000 --- a/langfuse/api/resources/utils/resources/pagination/types/meta_response.py +++ /dev/null @@ -1,62 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -from ......core.datetime_utils import serialize_datetime -from ......core.pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1 - - -class MetaResponse(pydantic_v1.BaseModel): - page: int = pydantic_v1.Field() - """ - current page number - """ - - limit: int = pydantic_v1.Field() - """ - number of items per page - """ - - total_items: int = pydantic_v1.Field(alias="totalItems") - """ - number of total items given the current filters/selection (if any) - """ - - total_pages: int = pydantic_v1.Field(alias="totalPages") - """ - number of total pages given the current limit - """ - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - kwargs_with_defaults_exclude_none: typing.Any = { - "by_alias": True, - "exclude_none": True, - **kwargs, - } - - return deep_union_pydantic_dicts( - super().dict(**kwargs_with_defaults_exclude_unset), - super().dict(**kwargs_with_defaults_exclude_none), - ) - - class Config: - frozen = True - smart_union = True - allow_population_by_field_name = True - populate_by_name = True - extra = pydantic_v1.Extra.allow - json_encoders = {dt.datetime: serialize_datetime} diff --git a/langfuse/api/scim/__init__.py b/langfuse/api/scim/__init__.py new file mode 100644 index 000000000..6c4126f3c --- /dev/null +++ b/langfuse/api/scim/__init__.py @@ -0,0 +1,94 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + AuthenticationScheme, + BulkConfig, + EmptyResponse, + FilterConfig, + ResourceMeta, + ResourceType, + ResourceTypesResponse, + SchemaExtension, + SchemaResource, + SchemasResponse, + ScimEmail, + ScimFeatureSupport, + ScimName, + ScimUser, + ScimUsersListResponse, + ServiceProviderConfig, + UserMeta, + ) +_dynamic_imports: typing.Dict[str, str] = { + "AuthenticationScheme": ".types", + "BulkConfig": ".types", + "EmptyResponse": ".types", + "FilterConfig": ".types", + "ResourceMeta": ".types", + "ResourceType": ".types", + "ResourceTypesResponse": ".types", + "SchemaExtension": ".types", + "SchemaResource": ".types", + "SchemasResponse": ".types", + "ScimEmail": ".types", + "ScimFeatureSupport": ".types", + "ScimName": ".types", + "ScimUser": ".types", + "ScimUsersListResponse": ".types", + "ServiceProviderConfig": ".types", + "UserMeta": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AuthenticationScheme", + "BulkConfig", + "EmptyResponse", + "FilterConfig", + "ResourceMeta", + "ResourceType", + "ResourceTypesResponse", + "SchemaExtension", + "SchemaResource", + "SchemasResponse", + "ScimEmail", + "ScimFeatureSupport", + "ScimName", + "ScimUser", + "ScimUsersListResponse", + "ServiceProviderConfig", + "UserMeta", +] diff --git a/langfuse/api/scim/client.py b/langfuse/api/scim/client.py new file mode 100644 index 000000000..30d0815bf --- /dev/null +++ b/langfuse/api/scim/client.py @@ -0,0 +1,686 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawScimClient, RawScimClient +from .types.empty_response import EmptyResponse +from .types.resource_types_response import ResourceTypesResponse +from .types.schemas_response import SchemasResponse +from .types.scim_email import ScimEmail +from .types.scim_name import ScimName +from .types.scim_user import ScimUser +from .types.scim_users_list_response import ScimUsersListResponse +from .types.service_provider_config import ServiceProviderConfig + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ScimClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawScimClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawScimClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawScimClient + """ + return self._raw_client + + def get_service_provider_config( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> ServiceProviderConfig: + """ + Get SCIM Service Provider Configuration (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ServiceProviderConfig + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.get_service_provider_config() + """ + _response = self._raw_client.get_service_provider_config( + request_options=request_options + ) + return _response.data + + def get_resource_types( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> ResourceTypesResponse: + """ + Get SCIM Resource Types (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ResourceTypesResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.get_resource_types() + """ + _response = self._raw_client.get_resource_types(request_options=request_options) + return _response.data + + def get_schemas( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> SchemasResponse: + """ + Get SCIM Schemas (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SchemasResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.get_schemas() + """ + _response = self._raw_client.get_schemas(request_options=request_options) + return _response.data + + def list_users( + self, + *, + filter: typing.Optional[str] = None, + start_index: typing.Optional[int] = None, + count: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScimUsersListResponse: + """ + List users in the organization (requires organization-scoped API key) + + Parameters + ---------- + filter : typing.Optional[str] + Filter expression (e.g. userName eq "value") + + start_index : typing.Optional[int] + 1-based index of the first result to return (default 1) + + count : typing.Optional[int] + Maximum number of results to return (default 100) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScimUsersListResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.list_users() + """ + _response = self._raw_client.list_users( + filter=filter, + start_index=start_index, + count=count, + request_options=request_options, + ) + return _response.data + + def create_user( + self, + *, + user_name: str, + name: ScimName, + emails: typing.Optional[typing.Sequence[ScimEmail]] = OMIT, + active: typing.Optional[bool] = OMIT, + password: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScimUser: + """ + Create a new user in the organization (requires organization-scoped API key) + + Parameters + ---------- + user_name : str + User's email address (required) + + name : ScimName + User's name information + + emails : typing.Optional[typing.Sequence[ScimEmail]] + User's email addresses + + active : typing.Optional[bool] + Whether the user is active + + password : typing.Optional[str] + Initial password for the user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScimUser + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.scim import ScimName + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.create_user( + user_name="userName", + name=ScimName(), + ) + """ + _response = self._raw_client.create_user( + user_name=user_name, + name=name, + emails=emails, + active=active, + password=password, + request_options=request_options, + ) + return _response.data + + def get_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ScimUser: + """ + Get a specific user by ID (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScimUser + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.get_user( + user_id="userId", + ) + """ + _response = self._raw_client.get_user(user_id, request_options=request_options) + return _response.data + + def delete_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> EmptyResponse: + """ + Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EmptyResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scim.delete_user( + user_id="userId", + ) + """ + _response = self._raw_client.delete_user( + user_id, request_options=request_options + ) + return _response.data + + +class AsyncScimClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawScimClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawScimClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawScimClient + """ + return self._raw_client + + async def get_service_provider_config( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> ServiceProviderConfig: + """ + Get SCIM Service Provider Configuration (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ServiceProviderConfig + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.get_service_provider_config() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_service_provider_config( + request_options=request_options + ) + return _response.data + + async def get_resource_types( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> ResourceTypesResponse: + """ + Get SCIM Resource Types (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ResourceTypesResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.get_resource_types() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_resource_types( + request_options=request_options + ) + return _response.data + + async def get_schemas( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> SchemasResponse: + """ + Get SCIM Schemas (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SchemasResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.get_schemas() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_schemas(request_options=request_options) + return _response.data + + async def list_users( + self, + *, + filter: typing.Optional[str] = None, + start_index: typing.Optional[int] = None, + count: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScimUsersListResponse: + """ + List users in the organization (requires organization-scoped API key) + + Parameters + ---------- + filter : typing.Optional[str] + Filter expression (e.g. userName eq "value") + + start_index : typing.Optional[int] + 1-based index of the first result to return (default 1) + + count : typing.Optional[int] + Maximum number of results to return (default 100) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScimUsersListResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.list_users() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_users( + filter=filter, + start_index=start_index, + count=count, + request_options=request_options, + ) + return _response.data + + async def create_user( + self, + *, + user_name: str, + name: ScimName, + emails: typing.Optional[typing.Sequence[ScimEmail]] = OMIT, + active: typing.Optional[bool] = OMIT, + password: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScimUser: + """ + Create a new user in the organization (requires organization-scoped API key) + + Parameters + ---------- + user_name : str + User's email address (required) + + name : ScimName + User's name information + + emails : typing.Optional[typing.Sequence[ScimEmail]] + User's email addresses + + active : typing.Optional[bool] + Whether the user is active + + password : typing.Optional[str] + Initial password for the user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScimUser + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.scim import ScimName + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.create_user( + user_name="userName", + name=ScimName(), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_user( + user_name=user_name, + name=name, + emails=emails, + active=active, + password=password, + request_options=request_options, + ) + return _response.data + + async def get_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ScimUser: + """ + Get a specific user by ID (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScimUser + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.get_user( + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_user( + user_id, request_options=request_options + ) + return _response.data + + async def delete_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> EmptyResponse: + """ + Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EmptyResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scim.delete_user( + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_user( + user_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/scim/raw_client.py b/langfuse/api/scim/raw_client.py new file mode 100644 index 000000000..e65f46592 --- /dev/null +++ b/langfuse/api/scim/raw_client.py @@ -0,0 +1,1528 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.empty_response import EmptyResponse +from .types.resource_types_response import ResourceTypesResponse +from .types.schemas_response import SchemasResponse +from .types.scim_email import ScimEmail +from .types.scim_name import ScimName +from .types.scim_user import ScimUser +from .types.scim_users_list_response import ScimUsersListResponse +from .types.service_provider_config import ServiceProviderConfig + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawScimClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_service_provider_config( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ServiceProviderConfig]: + """ + Get SCIM Service Provider Configuration (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ServiceProviderConfig] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/scim/ServiceProviderConfig", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ServiceProviderConfig, + parse_obj_as( + type_=ServiceProviderConfig, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_resource_types( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ResourceTypesResponse]: + """ + Get SCIM Resource Types (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ResourceTypesResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/scim/ResourceTypes", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ResourceTypesResponse, + parse_obj_as( + type_=ResourceTypesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_schemas( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SchemasResponse]: + """ + Get SCIM Schemas (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SchemasResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/scim/Schemas", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SchemasResponse, + parse_obj_as( + type_=SchemasResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list_users( + self, + *, + filter: typing.Optional[str] = None, + start_index: typing.Optional[int] = None, + count: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ScimUsersListResponse]: + """ + List users in the organization (requires organization-scoped API key) + + Parameters + ---------- + filter : typing.Optional[str] + Filter expression (e.g. userName eq "value") + + start_index : typing.Optional[int] + 1-based index of the first result to return (default 1) + + count : typing.Optional[int] + Maximum number of results to return (default 100) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScimUsersListResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/scim/Users", + method="GET", + params={ + "filter": filter, + "startIndex": start_index, + "count": count, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScimUsersListResponse, + parse_obj_as( + type_=ScimUsersListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def create_user( + self, + *, + user_name: str, + name: ScimName, + emails: typing.Optional[typing.Sequence[ScimEmail]] = OMIT, + active: typing.Optional[bool] = OMIT, + password: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ScimUser]: + """ + Create a new user in the organization (requires organization-scoped API key) + + Parameters + ---------- + user_name : str + User's email address (required) + + name : ScimName + User's name information + + emails : typing.Optional[typing.Sequence[ScimEmail]] + User's email addresses + + active : typing.Optional[bool] + Whether the user is active + + password : typing.Optional[str] + Initial password for the user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScimUser] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/scim/Users", + method="POST", + json={ + "userName": user_name, + "name": convert_and_respect_annotation_metadata( + object_=name, annotation=ScimName, direction="write" + ), + "emails": convert_and_respect_annotation_metadata( + object_=emails, + annotation=typing.Sequence[ScimEmail], + direction="write", + ), + "active": active, + "password": password, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScimUser, + parse_obj_as( + type_=ScimUser, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ScimUser]: + """ + Get a specific user by ID (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScimUser] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/scim/Users/{jsonable_encoder(user_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScimUser, + parse_obj_as( + type_=ScimUser, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[EmptyResponse]: + """ + Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EmptyResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/scim/Users/{jsonable_encoder(user_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EmptyResponse, + parse_obj_as( + type_=EmptyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawScimClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_service_provider_config( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ServiceProviderConfig]: + """ + Get SCIM Service Provider Configuration (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ServiceProviderConfig] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/scim/ServiceProviderConfig", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ServiceProviderConfig, + parse_obj_as( + type_=ServiceProviderConfig, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_resource_types( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ResourceTypesResponse]: + """ + Get SCIM Resource Types (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ResourceTypesResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/scim/ResourceTypes", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ResourceTypesResponse, + parse_obj_as( + type_=ResourceTypesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_schemas( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SchemasResponse]: + """ + Get SCIM Schemas (requires organization-scoped API key) + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SchemasResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/scim/Schemas", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SchemasResponse, + parse_obj_as( + type_=SchemasResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list_users( + self, + *, + filter: typing.Optional[str] = None, + start_index: typing.Optional[int] = None, + count: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ScimUsersListResponse]: + """ + List users in the organization (requires organization-scoped API key) + + Parameters + ---------- + filter : typing.Optional[str] + Filter expression (e.g. userName eq "value") + + start_index : typing.Optional[int] + 1-based index of the first result to return (default 1) + + count : typing.Optional[int] + Maximum number of results to return (default 100) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScimUsersListResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/scim/Users", + method="GET", + params={ + "filter": filter, + "startIndex": start_index, + "count": count, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScimUsersListResponse, + parse_obj_as( + type_=ScimUsersListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def create_user( + self, + *, + user_name: str, + name: ScimName, + emails: typing.Optional[typing.Sequence[ScimEmail]] = OMIT, + active: typing.Optional[bool] = OMIT, + password: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ScimUser]: + """ + Create a new user in the organization (requires organization-scoped API key) + + Parameters + ---------- + user_name : str + User's email address (required) + + name : ScimName + User's name information + + emails : typing.Optional[typing.Sequence[ScimEmail]] + User's email addresses + + active : typing.Optional[bool] + Whether the user is active + + password : typing.Optional[str] + Initial password for the user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScimUser] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/scim/Users", + method="POST", + json={ + "userName": user_name, + "name": convert_and_respect_annotation_metadata( + object_=name, annotation=ScimName, direction="write" + ), + "emails": convert_and_respect_annotation_metadata( + object_=emails, + annotation=typing.Sequence[ScimEmail], + direction="write", + ), + "active": active, + "password": password, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScimUser, + parse_obj_as( + type_=ScimUser, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ScimUser]: + """ + Get a specific user by ID (requires organization-scoped API key) + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScimUser] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/scim/Users/{jsonable_encoder(user_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScimUser, + parse_obj_as( + type_=ScimUser, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_user( + self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[EmptyResponse]: + """ + Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. + + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EmptyResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/scim/Users/{jsonable_encoder(user_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EmptyResponse, + parse_obj_as( + type_=EmptyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/scim/types/__init__.py b/langfuse/api/scim/types/__init__.py new file mode 100644 index 000000000..9d6483e3d --- /dev/null +++ b/langfuse/api/scim/types/__init__.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .authentication_scheme import AuthenticationScheme + from .bulk_config import BulkConfig + from .empty_response import EmptyResponse + from .filter_config import FilterConfig + from .resource_meta import ResourceMeta + from .resource_type import ResourceType + from .resource_types_response import ResourceTypesResponse + from .schema_extension import SchemaExtension + from .schema_resource import SchemaResource + from .schemas_response import SchemasResponse + from .scim_email import ScimEmail + from .scim_feature_support import ScimFeatureSupport + from .scim_name import ScimName + from .scim_user import ScimUser + from .scim_users_list_response import ScimUsersListResponse + from .service_provider_config import ServiceProviderConfig + from .user_meta import UserMeta +_dynamic_imports: typing.Dict[str, str] = { + "AuthenticationScheme": ".authentication_scheme", + "BulkConfig": ".bulk_config", + "EmptyResponse": ".empty_response", + "FilterConfig": ".filter_config", + "ResourceMeta": ".resource_meta", + "ResourceType": ".resource_type", + "ResourceTypesResponse": ".resource_types_response", + "SchemaExtension": ".schema_extension", + "SchemaResource": ".schema_resource", + "SchemasResponse": ".schemas_response", + "ScimEmail": ".scim_email", + "ScimFeatureSupport": ".scim_feature_support", + "ScimName": ".scim_name", + "ScimUser": ".scim_user", + "ScimUsersListResponse": ".scim_users_list_response", + "ServiceProviderConfig": ".service_provider_config", + "UserMeta": ".user_meta", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AuthenticationScheme", + "BulkConfig", + "EmptyResponse", + "FilterConfig", + "ResourceMeta", + "ResourceType", + "ResourceTypesResponse", + "SchemaExtension", + "SchemaResource", + "SchemasResponse", + "ScimEmail", + "ScimFeatureSupport", + "ScimName", + "ScimUser", + "ScimUsersListResponse", + "ServiceProviderConfig", + "UserMeta", +] diff --git a/langfuse/api/scim/types/authentication_scheme.py b/langfuse/api/scim/types/authentication_scheme.py new file mode 100644 index 000000000..fc1fceb14 --- /dev/null +++ b/langfuse/api/scim/types/authentication_scheme.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class AuthenticationScheme(UniversalBaseModel): + name: str + description: str + spec_uri: typing_extensions.Annotated[str, FieldMetadata(alias="specUri")] + type: str + primary: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/bulk_config.py b/langfuse/api/scim/types/bulk_config.py new file mode 100644 index 000000000..4a3ae719f --- /dev/null +++ b/langfuse/api/scim/types/bulk_config.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class BulkConfig(UniversalBaseModel): + supported: bool + max_operations: typing_extensions.Annotated[ + int, FieldMetadata(alias="maxOperations") + ] + max_payload_size: typing_extensions.Annotated[ + int, FieldMetadata(alias="maxPayloadSize") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/empty_response.py b/langfuse/api/scim/types/empty_response.py new file mode 100644 index 000000000..1371104f8 --- /dev/null +++ b/langfuse/api/scim/types/empty_response.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class EmptyResponse(UniversalBaseModel): + """ + Empty response for 204 No Content responses + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/filter_config.py b/langfuse/api/scim/types/filter_config.py new file mode 100644 index 000000000..ba9986e56 --- /dev/null +++ b/langfuse/api/scim/types/filter_config.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class FilterConfig(UniversalBaseModel): + supported: bool + max_results: typing_extensions.Annotated[int, FieldMetadata(alias="maxResults")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/resource_meta.py b/langfuse/api/scim/types/resource_meta.py new file mode 100644 index 000000000..99be2a96e --- /dev/null +++ b/langfuse/api/scim/types/resource_meta.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class ResourceMeta(UniversalBaseModel): + resource_type: typing_extensions.Annotated[str, FieldMetadata(alias="resourceType")] + location: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/resource_type.py b/langfuse/api/scim/types/resource_type.py new file mode 100644 index 000000000..9913c465d --- /dev/null +++ b/langfuse/api/scim/types/resource_type.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .resource_meta import ResourceMeta +from .schema_extension import SchemaExtension + + +class ResourceType(UniversalBaseModel): + schemas: typing.Optional[typing.List[str]] = None + id: str + name: str + endpoint: str + description: str + schema_: typing_extensions.Annotated[str, FieldMetadata(alias="schema")] + schema_extensions: typing_extensions.Annotated[ + typing.List[SchemaExtension], FieldMetadata(alias="schemaExtensions") + ] + meta: ResourceMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/resource_types_response.py b/langfuse/api/scim/types/resource_types_response.py new file mode 100644 index 000000000..8ff1c47d9 --- /dev/null +++ b/langfuse/api/scim/types/resource_types_response.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .resource_type import ResourceType + + +class ResourceTypesResponse(UniversalBaseModel): + schemas: typing.List[str] + total_results: typing_extensions.Annotated[int, FieldMetadata(alias="totalResults")] + resources: typing_extensions.Annotated[ + typing.List[ResourceType], FieldMetadata(alias="Resources") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/schema_extension.py b/langfuse/api/scim/types/schema_extension.py new file mode 100644 index 000000000..4a09d6192 --- /dev/null +++ b/langfuse/api/scim/types/schema_extension.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class SchemaExtension(UniversalBaseModel): + schema_: typing_extensions.Annotated[str, FieldMetadata(alias="schema")] + required: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/schema_resource.py b/langfuse/api/scim/types/schema_resource.py new file mode 100644 index 000000000..cafc07dcb --- /dev/null +++ b/langfuse/api/scim/types/schema_resource.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .resource_meta import ResourceMeta + + +class SchemaResource(UniversalBaseModel): + id: str + name: str + description: str + attributes: typing.List[typing.Any] + meta: ResourceMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/schemas_response.py b/langfuse/api/scim/types/schemas_response.py new file mode 100644 index 000000000..3162f9431 --- /dev/null +++ b/langfuse/api/scim/types/schemas_response.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .schema_resource import SchemaResource + + +class SchemasResponse(UniversalBaseModel): + schemas: typing.List[str] + total_results: typing_extensions.Annotated[int, FieldMetadata(alias="totalResults")] + resources: typing_extensions.Annotated[ + typing.List[SchemaResource], FieldMetadata(alias="Resources") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/scim_email.py b/langfuse/api/scim/types/scim_email.py new file mode 100644 index 000000000..7d589f0cf --- /dev/null +++ b/langfuse/api/scim/types/scim_email.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ScimEmail(UniversalBaseModel): + primary: bool + value: str + type: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/scim_feature_support.py b/langfuse/api/scim/types/scim_feature_support.py new file mode 100644 index 000000000..4c24a694d --- /dev/null +++ b/langfuse/api/scim/types/scim_feature_support.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ScimFeatureSupport(UniversalBaseModel): + supported: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/scim_name.py b/langfuse/api/scim/types/scim_name.py new file mode 100644 index 000000000..53d2d79e3 --- /dev/null +++ b/langfuse/api/scim/types/scim_name.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ScimName(UniversalBaseModel): + formatted: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/scim_user.py b/langfuse/api/scim/types/scim_user.py new file mode 100644 index 000000000..22d6d50fe --- /dev/null +++ b/langfuse/api/scim/types/scim_user.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .scim_email import ScimEmail +from .scim_name import ScimName +from .user_meta import UserMeta + + +class ScimUser(UniversalBaseModel): + schemas: typing.List[str] + id: str + user_name: typing_extensions.Annotated[str, FieldMetadata(alias="userName")] + name: ScimName + emails: typing.List[ScimEmail] + meta: UserMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/scim_users_list_response.py b/langfuse/api/scim/types/scim_users_list_response.py new file mode 100644 index 000000000..bcfba30bb --- /dev/null +++ b/langfuse/api/scim/types/scim_users_list_response.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .scim_user import ScimUser + + +class ScimUsersListResponse(UniversalBaseModel): + schemas: typing.List[str] + total_results: typing_extensions.Annotated[int, FieldMetadata(alias="totalResults")] + start_index: typing_extensions.Annotated[int, FieldMetadata(alias="startIndex")] + items_per_page: typing_extensions.Annotated[ + int, FieldMetadata(alias="itemsPerPage") + ] + resources: typing_extensions.Annotated[ + typing.List[ScimUser], FieldMetadata(alias="Resources") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/service_provider_config.py b/langfuse/api/scim/types/service_provider_config.py new file mode 100644 index 000000000..48add080e --- /dev/null +++ b/langfuse/api/scim/types/service_provider_config.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .authentication_scheme import AuthenticationScheme +from .bulk_config import BulkConfig +from .filter_config import FilterConfig +from .resource_meta import ResourceMeta +from .scim_feature_support import ScimFeatureSupport + + +class ServiceProviderConfig(UniversalBaseModel): + schemas: typing.List[str] + documentation_uri: typing_extensions.Annotated[ + str, FieldMetadata(alias="documentationUri") + ] + patch: ScimFeatureSupport + bulk: BulkConfig + filter: FilterConfig + change_password: typing_extensions.Annotated[ + ScimFeatureSupport, FieldMetadata(alias="changePassword") + ] + sort: ScimFeatureSupport + etag: ScimFeatureSupport + authentication_schemes: typing_extensions.Annotated[ + typing.List[AuthenticationScheme], FieldMetadata(alias="authenticationSchemes") + ] + meta: ResourceMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scim/types/user_meta.py b/langfuse/api/scim/types/user_meta.py new file mode 100644 index 000000000..033ed4fa1 --- /dev/null +++ b/langfuse/api/scim/types/user_meta.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class UserMeta(UniversalBaseModel): + resource_type: typing_extensions.Annotated[str, FieldMetadata(alias="resourceType")] + created: typing.Optional[str] = None + last_modified: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="lastModified") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score/__init__.py b/langfuse/api/score/__init__.py new file mode 100644 index 000000000..3d0c7422a --- /dev/null +++ b/langfuse/api/score/__init__.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateScoreRequest, CreateScoreResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateScoreRequest": ".types", + "CreateScoreResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateScoreRequest", "CreateScoreResponse"] diff --git a/langfuse/api/score/client.py b/langfuse/api/score/client.py new file mode 100644 index 000000000..7a2ba1b83 --- /dev/null +++ b/langfuse/api/score/client.py @@ -0,0 +1,329 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..commons.types.create_score_value import CreateScoreValue +from ..commons.types.score_data_type import ScoreDataType +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawScoreClient, RawScoreClient +from .types.create_score_response import CreateScoreResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ScoreClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawScoreClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawScoreClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawScoreClient + """ + return self._raw_client + + def create( + self, + *, + name: str, + value: CreateScoreValue, + id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + session_id: typing.Optional[str] = OMIT, + observation_id: typing.Optional[str] = OMIT, + dataset_run_id: typing.Optional[str] = OMIT, + comment: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + environment: typing.Optional[str] = OMIT, + queue_id: typing.Optional[str] = OMIT, + data_type: typing.Optional[ScoreDataType] = OMIT, + config_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateScoreResponse: + """ + Create a score (supports both trace and session scores) + + Parameters + ---------- + name : str + + value : CreateScoreValue + The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + + id : typing.Optional[str] + + trace_id : typing.Optional[str] + + session_id : typing.Optional[str] + + observation_id : typing.Optional[str] + + dataset_run_id : typing.Optional[str] + + comment : typing.Optional[str] + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + + environment : typing.Optional[str] + The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + + queue_id : typing.Optional[str] + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + + data_type : typing.Optional[ScoreDataType] + The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. + + config_id : typing.Optional[str] + Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateScoreResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score.create( + name="name", + value=1.1, + ) + """ + _response = self._raw_client.create( + name=name, + value=value, + id=id, + trace_id=trace_id, + session_id=session_id, + observation_id=observation_id, + dataset_run_id=dataset_run_id, + comment=comment, + metadata=metadata, + environment=environment, + queue_id=queue_id, + data_type=data_type, + config_id=config_id, + request_options=request_options, + ) + return _response.data + + def delete( + self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> None: + """ + Delete a score (supports both trace and session scores) + + Parameters + ---------- + score_id : str + The unique langfuse identifier of a score + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score.delete( + score_id="scoreId", + ) + """ + _response = self._raw_client.delete(score_id, request_options=request_options) + return _response.data + + +class AsyncScoreClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawScoreClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawScoreClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawScoreClient + """ + return self._raw_client + + async def create( + self, + *, + name: str, + value: CreateScoreValue, + id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + session_id: typing.Optional[str] = OMIT, + observation_id: typing.Optional[str] = OMIT, + dataset_run_id: typing.Optional[str] = OMIT, + comment: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + environment: typing.Optional[str] = OMIT, + queue_id: typing.Optional[str] = OMIT, + data_type: typing.Optional[ScoreDataType] = OMIT, + config_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateScoreResponse: + """ + Create a score (supports both trace and session scores) + + Parameters + ---------- + name : str + + value : CreateScoreValue + The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + + id : typing.Optional[str] + + trace_id : typing.Optional[str] + + session_id : typing.Optional[str] + + observation_id : typing.Optional[str] + + dataset_run_id : typing.Optional[str] + + comment : typing.Optional[str] + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + + environment : typing.Optional[str] + The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + + queue_id : typing.Optional[str] + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + + data_type : typing.Optional[ScoreDataType] + The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. + + config_id : typing.Optional[str] + Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateScoreResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score.create( + name="name", + value=1.1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + value=value, + id=id, + trace_id=trace_id, + session_id=session_id, + observation_id=observation_id, + dataset_run_id=dataset_run_id, + comment=comment, + metadata=metadata, + environment=environment, + queue_id=queue_id, + data_type=data_type, + config_id=config_id, + request_options=request_options, + ) + return _response.data + + async def delete( + self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> None: + """ + Delete a score (supports both trace and session scores) + + Parameters + ---------- + score_id : str + The unique langfuse identifier of a score + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score.delete( + score_id="scoreId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete( + score_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/score/raw_client.py b/langfuse/api/score/raw_client.py new file mode 100644 index 000000000..7c20eee08 --- /dev/null +++ b/langfuse/api/score/raw_client.py @@ -0,0 +1,545 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.create_score_value import CreateScoreValue +from ..commons.types.score_data_type import ScoreDataType +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.create_score_response import CreateScoreResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawScoreClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + name: str, + value: CreateScoreValue, + id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + session_id: typing.Optional[str] = OMIT, + observation_id: typing.Optional[str] = OMIT, + dataset_run_id: typing.Optional[str] = OMIT, + comment: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + environment: typing.Optional[str] = OMIT, + queue_id: typing.Optional[str] = OMIT, + data_type: typing.Optional[ScoreDataType] = OMIT, + config_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateScoreResponse]: + """ + Create a score (supports both trace and session scores) + + Parameters + ---------- + name : str + + value : CreateScoreValue + The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + + id : typing.Optional[str] + + trace_id : typing.Optional[str] + + session_id : typing.Optional[str] + + observation_id : typing.Optional[str] + + dataset_run_id : typing.Optional[str] + + comment : typing.Optional[str] + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + + environment : typing.Optional[str] + The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + + queue_id : typing.Optional[str] + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + + data_type : typing.Optional[ScoreDataType] + The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. + + config_id : typing.Optional[str] + Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateScoreResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/scores", + method="POST", + json={ + "id": id, + "traceId": trace_id, + "sessionId": session_id, + "observationId": observation_id, + "datasetRunId": dataset_run_id, + "name": name, + "value": convert_and_respect_annotation_metadata( + object_=value, annotation=CreateScoreValue, direction="write" + ), + "comment": comment, + "metadata": metadata, + "environment": environment, + "queueId": queue_id, + "dataType": data_type, + "configId": config_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateScoreResponse, + parse_obj_as( + type_=CreateScoreResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[None]: + """ + Delete a score (supports both trace and session scores) + + Parameters + ---------- + score_id : str + The unique langfuse identifier of a score + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/scores/{jsonable_encoder(score_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawScoreClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + name: str, + value: CreateScoreValue, + id: typing.Optional[str] = OMIT, + trace_id: typing.Optional[str] = OMIT, + session_id: typing.Optional[str] = OMIT, + observation_id: typing.Optional[str] = OMIT, + dataset_run_id: typing.Optional[str] = OMIT, + comment: typing.Optional[str] = OMIT, + metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + environment: typing.Optional[str] = OMIT, + queue_id: typing.Optional[str] = OMIT, + data_type: typing.Optional[ScoreDataType] = OMIT, + config_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateScoreResponse]: + """ + Create a score (supports both trace and session scores) + + Parameters + ---------- + name : str + + value : CreateScoreValue + The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + + id : typing.Optional[str] + + trace_id : typing.Optional[str] + + session_id : typing.Optional[str] + + observation_id : typing.Optional[str] + + dataset_run_id : typing.Optional[str] + + comment : typing.Optional[str] + + metadata : typing.Optional[typing.Dict[str, typing.Any]] + + environment : typing.Optional[str] + The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + + queue_id : typing.Optional[str] + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + + data_type : typing.Optional[ScoreDataType] + The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. + + config_id : typing.Optional[str] + Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateScoreResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/scores", + method="POST", + json={ + "id": id, + "traceId": trace_id, + "sessionId": session_id, + "observationId": observation_id, + "datasetRunId": dataset_run_id, + "name": name, + "value": convert_and_respect_annotation_metadata( + object_=value, annotation=CreateScoreValue, direction="write" + ), + "comment": comment, + "metadata": metadata, + "environment": environment, + "queueId": queue_id, + "dataType": data_type, + "configId": config_id, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateScoreResponse, + parse_obj_as( + type_=CreateScoreResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[None]: + """ + Delete a score (supports both trace and session scores) + + Parameters + ---------- + score_id : str + The unique langfuse identifier of a score + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/scores/{jsonable_encoder(score_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/score/types/__init__.py b/langfuse/api/score/types/__init__.py new file mode 100644 index 000000000..4a759a978 --- /dev/null +++ b/langfuse/api/score/types/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_score_request import CreateScoreRequest + from .create_score_response import CreateScoreResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateScoreRequest": ".create_score_request", + "CreateScoreResponse": ".create_score_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateScoreRequest", "CreateScoreResponse"] diff --git a/langfuse/api/score/types/create_score_request.py b/langfuse/api/score/types/create_score_request.py new file mode 100644 index 000000000..5491031ca --- /dev/null +++ b/langfuse/api/score/types/create_score_request.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...commons.types.create_score_value import CreateScoreValue +from ...commons.types.score_data_type import ScoreDataType +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateScoreRequest(UniversalBaseModel): + """ + Examples + -------- + from langfuse.score import CreateScoreRequest + + CreateScoreRequest( + name="novelty", + value=0.9, + trace_id="cdef-1234-5678-90ab", + ) + """ + + id: typing.Optional[str] = None + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + value: CreateScoreValue = pydantic.Field() + """ + The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + """ + + comment: typing.Optional[str] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + environment: typing.Optional[str] = pydantic.Field(default=None) + """ + The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. + """ + + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = pydantic.Field(default=None) + """ + The annotation queue referenced by the score. Indicates if score was initially created while processing annotation queue. + """ + + data_type: typing_extensions.Annotated[ + typing.Optional[ScoreDataType], FieldMetadata(alias="dataType") + ] = pydantic.Field(default=None) + """ + The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. + """ + + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = pydantic.Field(default=None) + """ + Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score/types/create_score_response.py b/langfuse/api/score/types/create_score_response.py new file mode 100644 index 000000000..1c20c0f3a --- /dev/null +++ b/langfuse/api/score/types/create_score_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class CreateScoreResponse(UniversalBaseModel): + id: str = pydantic.Field() + """ + The id of the created object in Langfuse + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_configs/__init__.py b/langfuse/api/score_configs/__init__.py new file mode 100644 index 000000000..16f409522 --- /dev/null +++ b/langfuse/api/score_configs/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateScoreConfigRequest, ScoreConfigs, UpdateScoreConfigRequest +_dynamic_imports: typing.Dict[str, str] = { + "CreateScoreConfigRequest": ".types", + "ScoreConfigs": ".types", + "UpdateScoreConfigRequest": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateScoreConfigRequest", "ScoreConfigs", "UpdateScoreConfigRequest"] diff --git a/langfuse/api/score_configs/client.py b/langfuse/api/score_configs/client.py new file mode 100644 index 000000000..da6626043 --- /dev/null +++ b/langfuse/api/score_configs/client.py @@ -0,0 +1,526 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..commons.types.config_category import ConfigCategory +from ..commons.types.score_config import ScoreConfig +from ..commons.types.score_config_data_type import ScoreConfigDataType +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawScoreConfigsClient, RawScoreConfigsClient +from .types.score_configs import ScoreConfigs + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ScoreConfigsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawScoreConfigsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawScoreConfigsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawScoreConfigsClient + """ + return self._raw_client + + def create( + self, + *, + name: str, + data_type: ScoreConfigDataType, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfig: + """ + Create a score configuration (config). Score configs are used to define the structure of scores + + Parameters + ---------- + name : str + + data_type : ScoreConfigDataType + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.commons import ScoreConfigDataType + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_configs.create( + name="name", + data_type=ScoreConfigDataType.NUMERIC, + ) + """ + _response = self._raw_client.create( + name=name, + data_type=data_type, + categories=categories, + min_value=min_value, + max_value=max_value, + description=description, + request_options=request_options, + ) + return _response.data + + def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfigs: + """ + Get all score configs + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfigs + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_configs.get() + """ + _response = self._raw_client.get( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get_by_id( + self, config_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ScoreConfig: + """ + Get a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_configs.get_by_id( + config_id="configId", + ) + """ + _response = self._raw_client.get_by_id( + config_id, request_options=request_options + ) + return _response.data + + def update( + self, + config_id: str, + *, + is_archived: typing.Optional[bool] = OMIT, + name: typing.Optional[str] = OMIT, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfig: + """ + Update a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + is_archived : typing.Optional[bool] + The status of the score config showing if it is archived or not + + name : typing.Optional[str] + The name of the score config + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_configs.update( + config_id="configId", + ) + """ + _response = self._raw_client.update( + config_id, + is_archived=is_archived, + name=name, + categories=categories, + min_value=min_value, + max_value=max_value, + description=description, + request_options=request_options, + ) + return _response.data + + +class AsyncScoreConfigsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawScoreConfigsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawScoreConfigsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawScoreConfigsClient + """ + return self._raw_client + + async def create( + self, + *, + name: str, + data_type: ScoreConfigDataType, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfig: + """ + Create a score configuration (config). Score configs are used to define the structure of scores + + Parameters + ---------- + name : str + + data_type : ScoreConfigDataType + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.commons import ScoreConfigDataType + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_configs.create( + name="name", + data_type=ScoreConfigDataType.NUMERIC, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + data_type=data_type, + categories=categories, + min_value=min_value, + max_value=max_value, + description=description, + request_options=request_options, + ) + return _response.data + + async def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfigs: + """ + Get all score configs + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfigs + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_configs.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get_by_id( + self, config_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ScoreConfig: + """ + Get a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_configs.get_by_id( + config_id="configId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_by_id( + config_id, request_options=request_options + ) + return _response.data + + async def update( + self, + config_id: str, + *, + is_archived: typing.Optional[bool] = OMIT, + name: typing.Optional[str] = OMIT, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ScoreConfig: + """ + Update a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + is_archived : typing.Optional[bool] + The status of the score config showing if it is archived or not + + name : typing.Optional[str] + The name of the score config + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ScoreConfig + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_configs.update( + config_id="configId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + config_id, + is_archived=is_archived, + name=name, + categories=categories, + min_value=min_value, + max_value=max_value, + description=description, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/score_configs/raw_client.py b/langfuse/api/score_configs/raw_client.py new file mode 100644 index 000000000..8021940c6 --- /dev/null +++ b/langfuse/api/score_configs/raw_client.py @@ -0,0 +1,1012 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.config_category import ConfigCategory +from ..commons.types.score_config import ScoreConfig +from ..commons.types.score_config_data_type import ScoreConfigDataType +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from .types.score_configs import ScoreConfigs + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawScoreConfigsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + name: str, + data_type: ScoreConfigDataType, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ScoreConfig]: + """ + Create a score configuration (config). Score configs are used to define the structure of scores + + Parameters + ---------- + name : str + + data_type : ScoreConfigDataType + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScoreConfig] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/score-configs", + method="POST", + json={ + "name": name, + "dataType": data_type, + "categories": convert_and_respect_annotation_metadata( + object_=categories, + annotation=typing.Sequence[ConfigCategory], + direction="write", + ), + "minValue": min_value, + "maxValue": max_value, + "description": description, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfig, + parse_obj_as( + type_=ScoreConfig, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ScoreConfigs]: + """ + Get all score configs + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScoreConfigs] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/score-configs", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfigs, + parse_obj_as( + type_=ScoreConfigs, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get_by_id( + self, config_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ScoreConfig]: + """ + Get a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScoreConfig] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/score-configs/{jsonable_encoder(config_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfig, + parse_obj_as( + type_=ScoreConfig, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def update( + self, + config_id: str, + *, + is_archived: typing.Optional[bool] = OMIT, + name: typing.Optional[str] = OMIT, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ScoreConfig]: + """ + Update a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + is_archived : typing.Optional[bool] + The status of the score config showing if it is archived or not + + name : typing.Optional[str] + The name of the score config + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ScoreConfig] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/score-configs/{jsonable_encoder(config_id)}", + method="PATCH", + json={ + "isArchived": is_archived, + "name": name, + "categories": convert_and_respect_annotation_metadata( + object_=categories, + annotation=typing.Sequence[ConfigCategory], + direction="write", + ), + "minValue": min_value, + "maxValue": max_value, + "description": description, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfig, + parse_obj_as( + type_=ScoreConfig, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawScoreConfigsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + name: str, + data_type: ScoreConfigDataType, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ScoreConfig]: + """ + Create a score configuration (config). Score configs are used to define the structure of scores + + Parameters + ---------- + name : str + + data_type : ScoreConfigDataType + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScoreConfig] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/score-configs", + method="POST", + json={ + "name": name, + "dataType": data_type, + "categories": convert_and_respect_annotation_metadata( + object_=categories, + annotation=typing.Sequence[ConfigCategory], + direction="write", + ), + "minValue": min_value, + "maxValue": max_value, + "description": description, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfig, + parse_obj_as( + type_=ScoreConfig, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ScoreConfigs]: + """ + Get all score configs + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScoreConfigs] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/score-configs", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfigs, + parse_obj_as( + type_=ScoreConfigs, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get_by_id( + self, config_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ScoreConfig]: + """ + Get a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScoreConfig] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/score-configs/{jsonable_encoder(config_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfig, + parse_obj_as( + type_=ScoreConfig, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def update( + self, + config_id: str, + *, + is_archived: typing.Optional[bool] = OMIT, + name: typing.Optional[str] = OMIT, + categories: typing.Optional[typing.Sequence[ConfigCategory]] = OMIT, + min_value: typing.Optional[float] = OMIT, + max_value: typing.Optional[float] = OMIT, + description: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ScoreConfig]: + """ + Update a score config + + Parameters + ---------- + config_id : str + The unique langfuse identifier of a score config + + is_archived : typing.Optional[bool] + The status of the score config showing if it is archived or not + + name : typing.Optional[str] + The name of the score config + + categories : typing.Optional[typing.Sequence[ConfigCategory]] + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + + min_value : typing.Optional[float] + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + + max_value : typing.Optional[float] + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + + description : typing.Optional[str] + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ScoreConfig] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/score-configs/{jsonable_encoder(config_id)}", + method="PATCH", + json={ + "isArchived": is_archived, + "name": name, + "categories": convert_and_respect_annotation_metadata( + object_=categories, + annotation=typing.Sequence[ConfigCategory], + direction="write", + ), + "minValue": min_value, + "maxValue": max_value, + "description": description, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ScoreConfig, + parse_obj_as( + type_=ScoreConfig, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/score_configs/types/__init__.py b/langfuse/api/score_configs/types/__init__.py new file mode 100644 index 000000000..10ef4f679 --- /dev/null +++ b/langfuse/api/score_configs/types/__init__.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_score_config_request import CreateScoreConfigRequest + from .score_configs import ScoreConfigs + from .update_score_config_request import UpdateScoreConfigRequest +_dynamic_imports: typing.Dict[str, str] = { + "CreateScoreConfigRequest": ".create_score_config_request", + "ScoreConfigs": ".score_configs", + "UpdateScoreConfigRequest": ".update_score_config_request", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateScoreConfigRequest", "ScoreConfigs", "UpdateScoreConfigRequest"] diff --git a/langfuse/api/score_configs/types/create_score_config_request.py b/langfuse/api/score_configs/types/create_score_config_request.py new file mode 100644 index 000000000..1c23fd91e --- /dev/null +++ b/langfuse/api/score_configs/types/create_score_config_request.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...commons.types.config_category import ConfigCategory +from ...commons.types.score_config_data_type import ScoreConfigDataType +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class CreateScoreConfigRequest(UniversalBaseModel): + name: str + data_type: typing_extensions.Annotated[ + ScoreConfigDataType, FieldMetadata(alias="dataType") + ] + categories: typing.Optional[typing.List[ConfigCategory]] = pydantic.Field( + default=None + ) + """ + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + """ + + min_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="minValue") + ] = pydantic.Field(default=None) + """ + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + """ + + max_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="maxValue") + ] = pydantic.Field(default=None) + """ + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_configs/types/score_configs.py b/langfuse/api/score_configs/types/score_configs.py new file mode 100644 index 000000000..d19763e9f --- /dev/null +++ b/langfuse/api/score_configs/types/score_configs.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.score_config import ScoreConfig +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class ScoreConfigs(UniversalBaseModel): + data: typing.List[ScoreConfig] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_configs/types/update_score_config_request.py b/langfuse/api/score_configs/types/update_score_config_request.py new file mode 100644 index 000000000..5237c544f --- /dev/null +++ b/langfuse/api/score_configs/types/update_score_config_request.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...commons.types.config_category import ConfigCategory +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class UpdateScoreConfigRequest(UniversalBaseModel): + is_archived: typing_extensions.Annotated[ + typing.Optional[bool], FieldMetadata(alias="isArchived") + ] = pydantic.Field(default=None) + """ + The status of the score config showing if it is archived or not + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + The name of the score config + """ + + categories: typing.Optional[typing.List[ConfigCategory]] = pydantic.Field( + default=None + ) + """ + Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed + """ + + min_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="minValue") + ] = pydantic.Field(default=None) + """ + Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞ + """ + + max_value: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="maxValue") + ] = pydantic.Field(default=None) + """ + Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞ + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_v2/__init__.py b/langfuse/api/score_v2/__init__.py new file mode 100644 index 000000000..d320aecfc --- /dev/null +++ b/langfuse/api/score_v2/__init__.py @@ -0,0 +1,76 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + GetScoresResponse, + GetScoresResponseData, + GetScoresResponseDataBoolean, + GetScoresResponseDataCategorical, + GetScoresResponseDataCorrection, + GetScoresResponseDataNumeric, + GetScoresResponseData_Boolean, + GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, + GetScoresResponseData_Numeric, + GetScoresResponseTraceData, + ) +_dynamic_imports: typing.Dict[str, str] = { + "GetScoresResponse": ".types", + "GetScoresResponseData": ".types", + "GetScoresResponseDataBoolean": ".types", + "GetScoresResponseDataCategorical": ".types", + "GetScoresResponseDataCorrection": ".types", + "GetScoresResponseDataNumeric": ".types", + "GetScoresResponseData_Boolean": ".types", + "GetScoresResponseData_Categorical": ".types", + "GetScoresResponseData_Correction": ".types", + "GetScoresResponseData_Numeric": ".types", + "GetScoresResponseTraceData": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GetScoresResponse", + "GetScoresResponseData", + "GetScoresResponseDataBoolean", + "GetScoresResponseDataCategorical", + "GetScoresResponseDataCorrection", + "GetScoresResponseDataNumeric", + "GetScoresResponseData_Boolean", + "GetScoresResponseData_Categorical", + "GetScoresResponseData_Correction", + "GetScoresResponseData_Numeric", + "GetScoresResponseTraceData", +] diff --git a/langfuse/api/score_v2/client.py b/langfuse/api/score_v2/client.py new file mode 100644 index 000000000..c30afe18b --- /dev/null +++ b/langfuse/api/score_v2/client.py @@ -0,0 +1,410 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..commons.types.score import Score +from ..commons.types.score_data_type import ScoreDataType +from ..commons.types.score_source import ScoreSource +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawScoreV2Client, RawScoreV2Client +from .types.get_scores_response import GetScoresResponse + + +class ScoreV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawScoreV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawScoreV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawScoreV2Client + """ + return self._raw_client + + def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + user_id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + source: typing.Optional[ScoreSource] = None, + operator: typing.Optional[str] = None, + value: typing.Optional[float] = None, + score_ids: typing.Optional[str] = None, + config_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + dataset_run_id: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, + queue_id: typing.Optional[str] = None, + data_type: typing.Optional[ScoreDataType] = None, + trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + fields: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetScoresResponse: + """ + Get a list of scores (supports both trace and session scores) + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + user_id : typing.Optional[str] + Retrieve only scores with this userId associated to the trace. + + name : typing.Optional[str] + Retrieve only scores with this name. + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include scores created on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include scores created before a certain datetime (ISO 8601) + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for scores where the environment is one of the provided values. + + source : typing.Optional[ScoreSource] + Retrieve only scores from a specific source. + + operator : typing.Optional[str] + Retrieve only scores with value. + + value : typing.Optional[float] + Retrieve only scores with value. + + score_ids : typing.Optional[str] + Comma-separated list of score IDs to limit the results to. + + config_id : typing.Optional[str] + Retrieve only scores with a specific configId. + + session_id : typing.Optional[str] + Retrieve only scores with a specific sessionId. + + dataset_run_id : typing.Optional[str] + Retrieve only scores with a specific datasetRunId. + + trace_id : typing.Optional[str] + Retrieve only scores with a specific traceId. + + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter scores by. + + queue_id : typing.Optional[str] + Retrieve only scores with a specific annotation queueId. + + data_type : typing.Optional[ScoreDataType] + Retrieve only scores with a specific dataType. + + trace_tags : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Only scores linked to traces that include all of these tags will be returned. + + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetScoresResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_v2.get() + """ + _response = self._raw_client.get( + page=page, + limit=limit, + user_id=user_id, + name=name, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + environment=environment, + source=source, + operator=operator, + value=value, + score_ids=score_ids, + config_id=config_id, + session_id=session_id, + dataset_run_id=dataset_run_id, + trace_id=trace_id, + observation_id=observation_id, + queue_id=queue_id, + data_type=data_type, + trace_tags=trace_tags, + fields=fields, + request_options=request_options, + ) + return _response.data + + def get_by_id( + self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> Score: + """ + Get a score (supports both trace and session scores) + + Parameters + ---------- + score_id : str + The unique langfuse identifier of a score + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Score + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.score_v2.get_by_id( + score_id="scoreId", + ) + """ + _response = self._raw_client.get_by_id( + score_id, request_options=request_options + ) + return _response.data + + +class AsyncScoreV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawScoreV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawScoreV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawScoreV2Client + """ + return self._raw_client + + async def get( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + user_id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + source: typing.Optional[ScoreSource] = None, + operator: typing.Optional[str] = None, + value: typing.Optional[float] = None, + score_ids: typing.Optional[str] = None, + config_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + dataset_run_id: typing.Optional[str] = None, + trace_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, + queue_id: typing.Optional[str] = None, + data_type: typing.Optional[ScoreDataType] = None, + trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + fields: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetScoresResponse: + """ + Get a list of scores (supports both trace and session scores) + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1. + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + user_id : typing.Optional[str] + Retrieve only scores with this userId associated to the trace. + + name : typing.Optional[str] + Retrieve only scores with this name. + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include scores created on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include scores created before a certain datetime (ISO 8601) + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for scores where the environment is one of the provided values. + + source : typing.Optional[ScoreSource] + Retrieve only scores from a specific source. + + operator : typing.Optional[str] + Retrieve only scores with value. + + value : typing.Optional[float] + Retrieve only scores with value. + + score_ids : typing.Optional[str] + Comma-separated list of score IDs to limit the results to. + + config_id : typing.Optional[str] + Retrieve only scores with a specific configId. + + session_id : typing.Optional[str] + Retrieve only scores with a specific sessionId. + + dataset_run_id : typing.Optional[str] + Retrieve only scores with a specific datasetRunId. + + trace_id : typing.Optional[str] + Retrieve only scores with a specific traceId. + + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter scores by. + + queue_id : typing.Optional[str] + Retrieve only scores with a specific annotation queueId. + + data_type : typing.Optional[ScoreDataType] + Retrieve only scores with a specific dataType. + + trace_tags : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Only scores linked to traces that include all of these tags will be returned. + + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetScoresResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_v2.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + page=page, + limit=limit, + user_id=user_id, + name=name, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + environment=environment, + source=source, + operator=operator, + value=value, + score_ids=score_ids, + config_id=config_id, + session_id=session_id, + dataset_run_id=dataset_run_id, + trace_id=trace_id, + observation_id=observation_id, + queue_id=queue_id, + data_type=data_type, + trace_tags=trace_tags, + fields=fields, + request_options=request_options, + ) + return _response.data + + async def get_by_id( + self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> Score: + """ + Get a score (supports both trace and session scores) + + Parameters + ---------- + score_id : str + The unique langfuse identifier of a score + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Score + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.score_v2.get_by_id( + score_id="scoreId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_by_id( + score_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/resources/score_v_2/client.py b/langfuse/api/score_v2/raw_client.py similarity index 59% rename from langfuse/api/resources/score_v_2/client.py rename to langfuse/api/score_v2/raw_client.py index 7372a7784..b47a6cd1d 100644 --- a/langfuse/api/resources/score_v_2/client.py +++ b/langfuse/api/score_v2/raw_client.py @@ -4,12 +4,6 @@ import typing from json.decoder import JSONDecodeError -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions from ..commons.errors.access_denied_error import AccessDeniedError from ..commons.errors.error import Error from ..commons.errors.method_not_allowed_error import MethodNotAllowedError @@ -18,10 +12,17 @@ from ..commons.types.score import Score from ..commons.types.score_data_type import ScoreDataType from ..commons.types.score_source import ScoreSource +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions from .types.get_scores_response import GetScoresResponse -class ScoreV2Client: +class RawScoreV2Client: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper @@ -49,7 +50,7 @@ def get( trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> GetScoresResponse: + ) -> HttpResponse[GetScoresResponse]: """ Get a list of scores (supports both trace and session scores) @@ -120,21 +121,7 @@ def get( Returns ------- - GetScoresResponse - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score_v_2.get() + HttpResponse[GetScoresResponse] """ _response = self._client_wrapper.httpx_client.request( "api/public/v2/scores", @@ -169,33 +156,85 @@ def get( ) try: if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(GetScoresResponse, _response.json()) # type: ignore + _data = typing.cast( + GetScoresResponse, + parse_obj_as( + type_=GetScoresResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 401: raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 403: raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 405: raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 404: raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) def get_by_id( self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> Score: + ) -> HttpResponse[Score]: """ Get a score (supports both trace and session scores) @@ -209,23 +248,7 @@ def get_by_id( Returns ------- - Score - - Examples - -------- - from langfuse.client import FernLangfuse - - client = FernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.score_v_2.get_by_id( - score_id="scoreId", - ) + HttpResponse[Score] """ _response = self._client_wrapper.httpx_client.request( f"api/public/v2/scores/{jsonable_encoder(score_id)}", @@ -234,32 +257,84 @@ def get_by_id( ) try: if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Score, _response.json()) # type: ignore + _data = typing.cast( + Score, + parse_obj_as( + type_=Score, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 401: raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 403: raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 405: raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 404: raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) -class AsyncScoreV2Client: +class AsyncRawScoreV2Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper @@ -287,7 +362,7 @@ async def get( trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> GetScoresResponse: + ) -> AsyncHttpResponse[GetScoresResponse]: """ Get a list of scores (supports both trace and session scores) @@ -358,29 +433,7 @@ async def get( Returns ------- - GetScoresResponse - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score_v_2.get() - - - asyncio.run(main()) + AsyncHttpResponse[GetScoresResponse] """ _response = await self._client_wrapper.httpx_client.request( "api/public/v2/scores", @@ -415,33 +468,85 @@ async def main() -> None: ) try: if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(GetScoresResponse, _response.json()) # type: ignore + _data = typing.cast( + GetScoresResponse, + parse_obj_as( + type_=GetScoresResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 401: raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 403: raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 405: raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 404: raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) async def get_by_id( self, score_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> Score: + ) -> AsyncHttpResponse[Score]: """ Get a score (supports both trace and session scores) @@ -455,31 +560,7 @@ async def get_by_id( Returns ------- - Score - - Examples - -------- - import asyncio - - from langfuse.client import AsyncFernLangfuse - - client = AsyncFernLangfuse( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.score_v_2.get_by_id( - score_id="scoreId", - ) - - - asyncio.run(main()) + AsyncHttpResponse[Score] """ _response = await self._client_wrapper.httpx_client.request( f"api/public/v2/scores/{jsonable_encoder(score_id)}", @@ -488,26 +569,78 @@ async def main() -> None: ) try: if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Score, _response.json()) # type: ignore + _data = typing.cast( + Score, + parse_obj_as( + type_=Score, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 401: raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 403: raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 405: raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 404: raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/score_v2/types/__init__.py b/langfuse/api/score_v2/types/__init__.py new file mode 100644 index 000000000..3ee0246d4 --- /dev/null +++ b/langfuse/api/score_v2/types/__init__.py @@ -0,0 +1,76 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .get_scores_response import GetScoresResponse + from .get_scores_response_data import ( + GetScoresResponseData, + GetScoresResponseData_Boolean, + GetScoresResponseData_Categorical, + GetScoresResponseData_Correction, + GetScoresResponseData_Numeric, + ) + from .get_scores_response_data_boolean import GetScoresResponseDataBoolean + from .get_scores_response_data_categorical import GetScoresResponseDataCategorical + from .get_scores_response_data_correction import GetScoresResponseDataCorrection + from .get_scores_response_data_numeric import GetScoresResponseDataNumeric + from .get_scores_response_trace_data import GetScoresResponseTraceData +_dynamic_imports: typing.Dict[str, str] = { + "GetScoresResponse": ".get_scores_response", + "GetScoresResponseData": ".get_scores_response_data", + "GetScoresResponseDataBoolean": ".get_scores_response_data_boolean", + "GetScoresResponseDataCategorical": ".get_scores_response_data_categorical", + "GetScoresResponseDataCorrection": ".get_scores_response_data_correction", + "GetScoresResponseDataNumeric": ".get_scores_response_data_numeric", + "GetScoresResponseData_Boolean": ".get_scores_response_data", + "GetScoresResponseData_Categorical": ".get_scores_response_data", + "GetScoresResponseData_Correction": ".get_scores_response_data", + "GetScoresResponseData_Numeric": ".get_scores_response_data", + "GetScoresResponseTraceData": ".get_scores_response_trace_data", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GetScoresResponse", + "GetScoresResponseData", + "GetScoresResponseDataBoolean", + "GetScoresResponseDataCategorical", + "GetScoresResponseDataCorrection", + "GetScoresResponseDataNumeric", + "GetScoresResponseData_Boolean", + "GetScoresResponseData_Categorical", + "GetScoresResponseData_Correction", + "GetScoresResponseData_Numeric", + "GetScoresResponseTraceData", +] diff --git a/langfuse/api/score_v2/types/get_scores_response.py b/langfuse/api/score_v2/types/get_scores_response.py new file mode 100644 index 000000000..0ca4d0e40 --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse +from .get_scores_response_data import GetScoresResponseData + + +class GetScoresResponse(UniversalBaseModel): + data: typing.List[GetScoresResponseData] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_v2/types/get_scores_response_data.py b/langfuse/api/score_v2/types/get_scores_response_data.py new file mode 100644 index 000000000..d1cdda417 --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response_data.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.score_source import ScoreSource +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseData_Numeric(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["NUMERIC"], FieldMetadata(alias="dataType") + ] = "NUMERIC" + trace: typing.Optional[GetScoresResponseTraceData] = None + value: float + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class GetScoresResponseData_Categorical(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CATEGORICAL"], FieldMetadata(alias="dataType") + ] = "CATEGORICAL" + trace: typing.Optional[GetScoresResponseTraceData] = None + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class GetScoresResponseData_Boolean(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["BOOLEAN"], FieldMetadata(alias="dataType") + ] = "BOOLEAN" + trace: typing.Optional[GetScoresResponseTraceData] = None + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class GetScoresResponseData_Correction(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CORRECTION"], FieldMetadata(alias="dataType") + ] = "CORRECTION" + trace: typing.Optional[GetScoresResponseTraceData] = None + value: float + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +GetScoresResponseData = typing_extensions.Annotated[ + typing.Union[ + GetScoresResponseData_Numeric, + GetScoresResponseData_Categorical, + GetScoresResponseData_Boolean, + GetScoresResponseData_Correction, + ], + pydantic.Field(discriminator="data_type"), +] diff --git a/langfuse/api/score_v2/types/get_scores_response_data_boolean.py b/langfuse/api/score_v2/types/get_scores_response_data_boolean.py new file mode 100644 index 000000000..eee645cd3 --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response_data_boolean.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.boolean_score import BooleanScore +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseDataBoolean(BooleanScore): + trace: typing.Optional[GetScoresResponseTraceData] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_v2/types/get_scores_response_data_categorical.py b/langfuse/api/score_v2/types/get_scores_response_data_categorical.py new file mode 100644 index 000000000..edd4e46fa --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response_data_categorical.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.categorical_score import CategoricalScore +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseDataCategorical(CategoricalScore): + trace: typing.Optional[GetScoresResponseTraceData] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_v2/types/get_scores_response_data_correction.py b/langfuse/api/score_v2/types/get_scores_response_data_correction.py new file mode 100644 index 000000000..df39c65ed --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response_data_correction.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.correction_score import CorrectionScore +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseDataCorrection(CorrectionScore): + trace: typing.Optional[GetScoresResponseTraceData] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_v2/types/get_scores_response_data_numeric.py b/langfuse/api/score_v2/types/get_scores_response_data_numeric.py new file mode 100644 index 000000000..350be0e0c --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response_data_numeric.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.numeric_score import NumericScore +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseDataNumeric(NumericScore): + trace: typing.Optional[GetScoresResponseTraceData] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/score_v2/types/get_scores_response_trace_data.py b/langfuse/api/score_v2/types/get_scores_response_trace_data.py new file mode 100644 index 000000000..674057172 --- /dev/null +++ b/langfuse/api/score_v2/types/get_scores_response_trace_data.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class GetScoresResponseTraceData(UniversalBaseModel): + user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="userId") + ] = pydantic.Field(default=None) + """ + The user ID associated with the trace referenced by score + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + A list of tags associated with the trace referenced by score + """ + + environment: typing.Optional[str] = pydantic.Field(default=None) + """ + The environment of the trace referenced by score + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/sessions/__init__.py b/langfuse/api/sessions/__init__.py new file mode 100644 index 000000000..89b7e63bc --- /dev/null +++ b/langfuse/api/sessions/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import PaginatedSessions +_dynamic_imports: typing.Dict[str, str] = {"PaginatedSessions": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["PaginatedSessions"] diff --git a/langfuse/api/sessions/client.py b/langfuse/api/sessions/client.py new file mode 100644 index 000000000..b99829feb --- /dev/null +++ b/langfuse/api/sessions/client.py @@ -0,0 +1,262 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..commons.types.session_with_traces import SessionWithTraces +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawSessionsClient, RawSessionsClient +from .types.paginated_sessions import PaginatedSessions + + +class SessionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSessionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSessionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSessionsClient + """ + return self._raw_client + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedSessions: + """ + Get sessions + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1 + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created before a certain datetime (ISO 8601) + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for sessions where the environment is one of the provided values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedSessions + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.sessions.list() + """ + _response = self._raw_client.list( + page=page, + limit=limit, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + environment=environment, + request_options=request_options, + ) + return _response.data + + def get( + self, + session_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> SessionWithTraces: + """ + Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` + + Parameters + ---------- + session_id : str + The unique id of a session + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionWithTraces + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.sessions.get( + session_id="sessionId", + ) + """ + _response = self._raw_client.get(session_id, request_options=request_options) + return _response.data + + +class AsyncSessionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSessionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSessionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSessionsClient + """ + return self._raw_client + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedSessions: + """ + Get sessions + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1 + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created before a certain datetime (ISO 8601) + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for sessions where the environment is one of the provided values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PaginatedSessions + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.sessions.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + page=page, + limit=limit, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + environment=environment, + request_options=request_options, + ) + return _response.data + + async def get( + self, + session_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> SessionWithTraces: + """ + Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` + + Parameters + ---------- + session_id : str + The unique id of a session + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionWithTraces + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.sessions.get( + session_id="sessionId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + session_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/sessions/raw_client.py b/langfuse/api/sessions/raw_client.py new file mode 100644 index 000000000..9c39a9a5a --- /dev/null +++ b/langfuse/api/sessions/raw_client.py @@ -0,0 +1,500 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.session_with_traces import SessionWithTraces +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.paginated_sessions import PaginatedSessions + + +class RawSessionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedSessions]: + """ + Get sessions + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1 + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created before a certain datetime (ISO 8601) + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for sessions where the environment is one of the provided values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PaginatedSessions] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/sessions", + method="GET", + params={ + "page": page, + "limit": limit, + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "environment": environment, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedSessions, + parse_obj_as( + type_=PaginatedSessions, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, + session_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SessionWithTraces]: + """ + Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` + + Parameters + ---------- + session_id : str + The unique id of a session + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SessionWithTraces] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/sessions/{jsonable_encoder(session_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionWithTraces, + parse_obj_as( + type_=SessionWithTraces, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawSessionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PaginatedSessions]: + """ + Get sessions + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1 + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include sessions created before a certain datetime (ISO 8601) + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for sessions where the environment is one of the provided values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PaginatedSessions] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/sessions", + method="GET", + params={ + "page": page, + "limit": limit, + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "environment": environment, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PaginatedSessions, + parse_obj_as( + type_=PaginatedSessions, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, + session_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SessionWithTraces]: + """ + Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` + + Parameters + ---------- + session_id : str + The unique id of a session + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionWithTraces] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/sessions/{jsonable_encoder(session_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionWithTraces, + parse_obj_as( + type_=SessionWithTraces, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/sessions/types/__init__.py b/langfuse/api/sessions/types/__init__.py new file mode 100644 index 000000000..45f21e139 --- /dev/null +++ b/langfuse/api/sessions/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .paginated_sessions import PaginatedSessions +_dynamic_imports: typing.Dict[str, str] = {"PaginatedSessions": ".paginated_sessions"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["PaginatedSessions"] diff --git a/langfuse/api/sessions/types/paginated_sessions.py b/langfuse/api/sessions/types/paginated_sessions.py new file mode 100644 index 000000000..5d7bd3886 --- /dev/null +++ b/langfuse/api/sessions/types/paginated_sessions.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.session import Session +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class PaginatedSessions(UniversalBaseModel): + data: typing.List[Session] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/trace/__init__.py b/langfuse/api/trace/__init__.py new file mode 100644 index 000000000..fc9889dfa --- /dev/null +++ b/langfuse/api/trace/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import DeleteTraceResponse, Sort, Traces +_dynamic_imports: typing.Dict[str, str] = { + "DeleteTraceResponse": ".types", + "Sort": ".types", + "Traces": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["DeleteTraceResponse", "Sort", "Traces"] diff --git a/langfuse/api/resources/trace/client.py b/langfuse/api/trace/client.py similarity index 61% rename from langfuse/api/resources/trace/client.py rename to langfuse/api/trace/client.py index e1f837f50..e5dcbb33c 100644 --- a/langfuse/api/resources/trace/client.py +++ b/langfuse/api/trace/client.py @@ -2,20 +2,11 @@ import datetime as dt import typing -from json.decoder import JSONDecodeError - -from ...core.api_error import ApiError -from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ...core.datetime_utils import serialize_datetime -from ...core.jsonable_encoder import jsonable_encoder -from ...core.pydantic_utilities import pydantic_v1 -from ...core.request_options import RequestOptions -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError + from ..commons.types.trace_with_full_details import TraceWithFullDetails +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawTraceClient, RawTraceClient from .types.delete_trace_response import DeleteTraceResponse from .types.traces import Traces @@ -25,7 +16,18 @@ class TraceClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = RawTraceClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTraceClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTraceClient + """ + return self._raw_client def get( self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None @@ -47,9 +49,9 @@ def get( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -61,36 +63,8 @@ def get( trace_id="traceId", ) """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/traces/{jsonable_encoder(trace_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(TraceWithFullDetails, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + _response = self._raw_client.get(trace_id, request_options=request_options) + return _response.data def delete( self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None @@ -112,9 +86,9 @@ def delete( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -126,36 +100,8 @@ def delete( trace_id="traceId", ) """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/traces/{jsonable_encoder(trace_id)}", - method="DELETE", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DeleteTraceResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + _response = self._raw_client.delete(trace_id, request_options=request_options) + return _response.data def list( self, @@ -333,9 +279,9 @@ def list( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -345,56 +291,24 @@ def list( ) client.trace.list() """ - _response = self._client_wrapper.httpx_client.request( - "api/public/traces", - method="GET", - params={ - "page": page, - "limit": limit, - "userId": user_id, - "name": name, - "sessionId": session_id, - "fromTimestamp": serialize_datetime(from_timestamp) - if from_timestamp is not None - else None, - "toTimestamp": serialize_datetime(to_timestamp) - if to_timestamp is not None - else None, - "orderBy": order_by, - "tags": tags, - "version": version, - "release": release, - "environment": environment, - "fields": fields, - "filter": filter, - }, + _response = self._raw_client.list( + page=page, + limit=limit, + user_id=user_id, + name=name, + session_id=session_id, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + order_by=order_by, + tags=tags, + version=version, + release=release, + environment=environment, + fields=fields, + filter=filter, request_options=request_options, ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Traces, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data def delete_multiple( self, @@ -419,9 +333,9 @@ def delete_multiple( Examples -------- - from langfuse.client import FernLangfuse + from langfuse import LangfuseAPI - client = FernLangfuse( + client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -433,43 +347,26 @@ def delete_multiple( trace_ids=["traceIds", "traceIds"], ) """ - _response = self._client_wrapper.httpx_client.request( - "api/public/traces", - method="DELETE", - json={"traceIds": trace_ids}, - request_options=request_options, - omit=OMIT, + _response = self._raw_client.delete_multiple( + trace_ids=trace_ids, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DeleteTraceResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data class AsyncTraceClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + self._raw_client = AsyncRawTraceClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTraceClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTraceClient + """ + return self._raw_client async def get( self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None @@ -493,9 +390,9 @@ async def get( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -513,36 +410,10 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/traces/{jsonable_encoder(trace_id)}", - method="GET", - request_options=request_options, + _response = await self._raw_client.get( + trace_id, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(TraceWithFullDetails, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data async def delete( self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None @@ -566,9 +437,9 @@ async def delete( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -586,36 +457,10 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/traces/{jsonable_encoder(trace_id)}", - method="DELETE", - request_options=request_options, + _response = await self._raw_client.delete( + trace_id, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DeleteTraceResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data async def list( self, @@ -795,9 +640,9 @@ async def list( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -813,56 +658,24 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/traces", - method="GET", - params={ - "page": page, - "limit": limit, - "userId": user_id, - "name": name, - "sessionId": session_id, - "fromTimestamp": serialize_datetime(from_timestamp) - if from_timestamp is not None - else None, - "toTimestamp": serialize_datetime(to_timestamp) - if to_timestamp is not None - else None, - "orderBy": order_by, - "tags": tags, - "version": version, - "release": release, - "environment": environment, - "fields": fields, - "filter": filter, - }, + _response = await self._raw_client.list( + page=page, + limit=limit, + user_id=user_id, + name=name, + session_id=session_id, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + order_by=order_by, + tags=tags, + version=version, + release=release, + environment=environment, + fields=fields, + filter=filter, request_options=request_options, ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(Traces, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data async def delete_multiple( self, @@ -889,9 +702,9 @@ async def delete_multiple( -------- import asyncio - from langfuse.client import AsyncFernLangfuse + from langfuse import AsyncLangfuseAPI - client = AsyncFernLangfuse( + client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", @@ -909,35 +722,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/traces", - method="DELETE", - json={"traceIds": trace_ids}, - request_options=request_options, - omit=OMIT, + _response = await self._raw_client.delete_multiple( + trace_ids=trace_ids, request_options=request_options ) - try: - if 200 <= _response.status_code < 300: - return pydantic_v1.parse_obj_as(DeleteTraceResponse, _response.json()) # type: ignore - if _response.status_code == 400: - raise Error(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore - if _response.status_code == 401: - raise UnauthorizedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 403: - raise AccessDeniedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 405: - raise MethodNotAllowedError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - if _response.status_code == 404: - raise NotFoundError( - pydantic_v1.parse_obj_as(typing.Any, _response.json()) - ) # type: ignore - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) + return _response.data diff --git a/langfuse/api/trace/raw_client.py b/langfuse/api/trace/raw_client.py new file mode 100644 index 000000000..10f3d15ec --- /dev/null +++ b/langfuse/api/trace/raw_client.py @@ -0,0 +1,1208 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..commons.types.trace_with_full_details import TraceWithFullDetails +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.delete_trace_response import DeleteTraceResponse +from .types.traces import Traces + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawTraceClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TraceWithFullDetails]: + """ + Get a specific trace + + Parameters + ---------- + trace_id : str + The unique langfuse identifier of a trace + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TraceWithFullDetails] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/traces/{jsonable_encoder(trace_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TraceWithFullDetails, + parse_obj_as( + type_=TraceWithFullDetails, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteTraceResponse]: + """ + Delete a specific trace + + Parameters + ---------- + trace_id : str + The unique langfuse identifier of the trace to delete + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteTraceResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/traces/{jsonable_encoder(trace_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteTraceResponse, + parse_obj_as( + type_=DeleteTraceResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + user_id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + order_by: typing.Optional[str] = None, + tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + version: typing.Optional[str] = None, + release: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Traces]: + """ + Get list of traces + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1 + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + user_id : typing.Optional[str] + + name : typing.Optional[str] + + session_id : typing.Optional[str] + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include traces with a trace.timestamp on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include traces with a trace.timestamp before a certain datetime (ISO 8601) + + order_by : typing.Optional[str] + Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc + + tags : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Only traces that include all of these tags will be returned. + + version : typing.Optional[str] + Optional filter to only include traces with a certain version. + + release : typing.Optional[str] + Optional filter to only include traces with a certain release. + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for traces where the environment is one of the provided values. + + fields : typing.Optional[str] + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Trace Fields + - `id` (string) - Trace ID + - `name` (string) - Trace name + - `timestamp` (datetime) - Trace timestamp + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + - `environment` (string) - Environment tag + - `version` (string) - Version tag + - `release` (string) - Release tag + - `tags` (arrayOptions) - Array of tags + - `bookmarked` (boolean) - Bookmark status + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Aggregated Metrics (from observations) + These metrics are aggregated from all observations within the trace: + - `latency` (number) - Latency in seconds (time from first observation start to last observation end) + - `inputTokens` (number) - Total input tokens across all observations + - `outputTokens` (number) - Total output tokens across all observations + - `totalTokens` (number) - Total tokens (alias: `tokens`) + - `inputCost` (number) - Total input cost in USD + - `outputCost` (number) - Total output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Observation Level Aggregations + These fields aggregate observation levels within the trace: + - `level` (string) - Highest severity level (ERROR > WARNING > DEFAULT > DEBUG) + - `warningCount` (number) - Count of WARNING level observations + - `errorCount` (number) - Count of ERROR level observations + - `defaultCount` (number) - Count of DEFAULT level observations + - `debugCount` (number) - Count of DEBUG level observations + + ### Scores (requires join with scores table) + - `scores_avg` (number) - Average of numeric scores (alias: `scores`) + - `score_categories` (categoryOptions) - Categorical score values + + ## Filter Examples + ```json + [ + { + "type": "datetime", + "column": "timestamp", + "operator": ">=", + "value": "2024-01-01T00:00:00Z" + }, + { + "type": "string", + "column": "userId", + "operator": "=", + "value": "user-123" + }, + { + "type": "number", + "column": "totalCost", + "operator": ">=", + "value": 0.01 + }, + { + "type": "arrayOptions", + "column": "tags", + "operator": "all of", + "value": ["production", "critical"] + }, + { + "type": "stringObject", + "column": "metadata", + "key": "customer_tier", + "operator": "=", + "value": "enterprise" + } + ] + ``` + + ## Performance Notes + - Filtering on `userId`, `sessionId`, or `metadata` may enable skip indexes for better query performance + - Score filters require a join with the scores table and may impact query performance + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Traces] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/traces", + method="GET", + params={ + "page": page, + "limit": limit, + "userId": user_id, + "name": name, + "sessionId": session_id, + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "orderBy": order_by, + "tags": tags, + "version": version, + "release": release, + "environment": environment, + "fields": fields, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Traces, + parse_obj_as( + type_=Traces, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete_multiple( + self, + *, + trace_ids: typing.Sequence[str], + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DeleteTraceResponse]: + """ + Delete multiple traces + + Parameters + ---------- + trace_ids : typing.Sequence[str] + List of trace IDs to delete + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteTraceResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/traces", + method="DELETE", + json={ + "traceIds": trace_ids, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteTraceResponse, + parse_obj_as( + type_=DeleteTraceResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawTraceClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TraceWithFullDetails]: + """ + Get a specific trace + + Parameters + ---------- + trace_id : str + The unique langfuse identifier of a trace + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TraceWithFullDetails] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/traces/{jsonable_encoder(trace_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TraceWithFullDetails, + parse_obj_as( + type_=TraceWithFullDetails, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteTraceResponse]: + """ + Delete a specific trace + + Parameters + ---------- + trace_id : str + The unique langfuse identifier of the trace to delete + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteTraceResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/traces/{jsonable_encoder(trace_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteTraceResponse, + parse_obj_as( + type_=DeleteTraceResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + user_id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + order_by: typing.Optional[str] = None, + tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + version: typing.Optional[str] = None, + release: typing.Optional[str] = None, + environment: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Traces]: + """ + Get list of traces + + Parameters + ---------- + page : typing.Optional[int] + Page number, starts at 1 + + limit : typing.Optional[int] + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + + user_id : typing.Optional[str] + + name : typing.Optional[str] + + session_id : typing.Optional[str] + + from_timestamp : typing.Optional[dt.datetime] + Optional filter to only include traces with a trace.timestamp on or after a certain datetime (ISO 8601) + + to_timestamp : typing.Optional[dt.datetime] + Optional filter to only include traces with a trace.timestamp before a certain datetime (ISO 8601) + + order_by : typing.Optional[str] + Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc + + tags : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Only traces that include all of these tags will be returned. + + version : typing.Optional[str] + Optional filter to only include traces with a certain version. + + release : typing.Optional[str] + Optional filter to only include traces with a certain release. + + environment : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Optional filter for traces where the environment is one of the provided values. + + fields : typing.Optional[str] + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + + filter : typing.Optional[str] + JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp). + + ## Filter Structure + Each filter condition has the following structure: + ```json + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + ``` + + ## Available Columns + + ### Core Trace Fields + - `id` (string) - Trace ID + - `name` (string) - Trace name + - `timestamp` (datetime) - Trace timestamp + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + - `environment` (string) - Environment tag + - `version` (string) - Version tag + - `release` (string) - Release tag + - `tags` (arrayOptions) - Array of tags + - `bookmarked` (boolean) - Bookmark status + + ### Structured Data + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + + ### Aggregated Metrics (from observations) + These metrics are aggregated from all observations within the trace: + - `latency` (number) - Latency in seconds (time from first observation start to last observation end) + - `inputTokens` (number) - Total input tokens across all observations + - `outputTokens` (number) - Total output tokens across all observations + - `totalTokens` (number) - Total tokens (alias: `tokens`) + - `inputCost` (number) - Total input cost in USD + - `outputCost` (number) - Total output cost in USD + - `totalCost` (number) - Total cost in USD + + ### Observation Level Aggregations + These fields aggregate observation levels within the trace: + - `level` (string) - Highest severity level (ERROR > WARNING > DEFAULT > DEBUG) + - `warningCount` (number) - Count of WARNING level observations + - `errorCount` (number) - Count of ERROR level observations + - `defaultCount` (number) - Count of DEFAULT level observations + - `debugCount` (number) - Count of DEBUG level observations + + ### Scores (requires join with scores table) + - `scores_avg` (number) - Average of numeric scores (alias: `scores`) + - `score_categories` (categoryOptions) - Categorical score values + + ## Filter Examples + ```json + [ + { + "type": "datetime", + "column": "timestamp", + "operator": ">=", + "value": "2024-01-01T00:00:00Z" + }, + { + "type": "string", + "column": "userId", + "operator": "=", + "value": "user-123" + }, + { + "type": "number", + "column": "totalCost", + "operator": ">=", + "value": 0.01 + }, + { + "type": "arrayOptions", + "column": "tags", + "operator": "all of", + "value": ["production", "critical"] + }, + { + "type": "stringObject", + "column": "metadata", + "key": "customer_tier", + "operator": "=", + "value": "enterprise" + } + ] + ``` + + ## Performance Notes + - Filtering on `userId`, `sessionId`, or `metadata` may enable skip indexes for better query performance + - Score filters require a join with the scores table and may impact query performance + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Traces] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/traces", + method="GET", + params={ + "page": page, + "limit": limit, + "userId": user_id, + "name": name, + "sessionId": session_id, + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "orderBy": order_by, + "tags": tags, + "version": version, + "release": release, + "environment": environment, + "fields": fields, + "filter": filter, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Traces, + parse_obj_as( + type_=Traces, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete_multiple( + self, + *, + trace_ids: typing.Sequence[str], + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DeleteTraceResponse]: + """ + Delete multiple traces + + Parameters + ---------- + trace_ids : typing.Sequence[str] + List of trace IDs to delete + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteTraceResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/traces", + method="DELETE", + json={ + "traceIds": trace_ids, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteTraceResponse, + parse_obj_as( + type_=DeleteTraceResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/trace/types/__init__.py b/langfuse/api/trace/types/__init__.py new file mode 100644 index 000000000..3eab30d21 --- /dev/null +++ b/langfuse/api/trace/types/__init__.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .delete_trace_response import DeleteTraceResponse + from .sort import Sort + from .traces import Traces +_dynamic_imports: typing.Dict[str, str] = { + "DeleteTraceResponse": ".delete_trace_response", + "Sort": ".sort", + "Traces": ".traces", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["DeleteTraceResponse", "Sort", "Traces"] diff --git a/langfuse/api/trace/types/delete_trace_response.py b/langfuse/api/trace/types/delete_trace_response.py new file mode 100644 index 000000000..173075a83 --- /dev/null +++ b/langfuse/api/trace/types/delete_trace_response.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class DeleteTraceResponse(UniversalBaseModel): + message: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/trace/types/sort.py b/langfuse/api/trace/types/sort.py new file mode 100644 index 000000000..b6b992870 --- /dev/null +++ b/langfuse/api/trace/types/sort.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class Sort(UniversalBaseModel): + id: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/trace/types/traces.py b/langfuse/api/trace/types/traces.py new file mode 100644 index 000000000..b559118e1 --- /dev/null +++ b/langfuse/api/trace/types/traces.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.trace_with_details import TraceWithDetails +from ...core.pydantic_utilities import UniversalBaseModel +from ...utils.pagination.types.meta_response import MetaResponse + + +class Traces(UniversalBaseModel): + data: typing.List[TraceWithDetails] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/utils/__init__.py b/langfuse/api/utils/__init__.py new file mode 100644 index 000000000..b272f64b5 --- /dev/null +++ b/langfuse/api/utils/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import pagination + from .pagination import MetaResponse +_dynamic_imports: typing.Dict[str, str] = { + "MetaResponse": ".pagination", + "pagination": ".pagination", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetaResponse", "pagination"] diff --git a/langfuse/api/utils/pagination/__init__.py b/langfuse/api/utils/pagination/__init__.py new file mode 100644 index 000000000..50821832d --- /dev/null +++ b/langfuse/api/utils/pagination/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import MetaResponse +_dynamic_imports: typing.Dict[str, str] = {"MetaResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetaResponse"] diff --git a/langfuse/api/utils/pagination/types/__init__.py b/langfuse/api/utils/pagination/types/__init__.py new file mode 100644 index 000000000..5c0d83028 --- /dev/null +++ b/langfuse/api/utils/pagination/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .meta_response import MetaResponse +_dynamic_imports: typing.Dict[str, str] = {"MetaResponse": ".meta_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["MetaResponse"] diff --git a/langfuse/api/utils/pagination/types/meta_response.py b/langfuse/api/utils/pagination/types/meta_response.py new file mode 100644 index 000000000..54d3847be --- /dev/null +++ b/langfuse/api/utils/pagination/types/meta_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata + + +class MetaResponse(UniversalBaseModel): + page: int = pydantic.Field() + """ + current page number + """ + + limit: int = pydantic.Field() + """ + number of items per page + """ + + total_items: typing_extensions.Annotated[int, FieldMetadata(alias="totalItems")] = ( + pydantic.Field() + ) + """ + number of total items given the current filters/selection (if any) + """ + + total_pages: typing_extensions.Annotated[int, FieldMetadata(alias="totalPages")] = ( + pydantic.Field() + ) + """ + number of total pages given the current limit + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 6de480e67..45cf25c04 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -24,7 +24,7 @@ cast, ) -from langfuse.api.resources.commons.types import ( +from langfuse.api import ( ObservationsView, TraceWithFullDetails, ) @@ -848,7 +848,7 @@ async def run_async( evaluators: List[EvaluatorFunction], filter: Optional[str] = None, fetch_batch_size: int = 50, - fetch_trace_fields: Optional[str] = None, + fetch_trace_fields: Optional[str] = "io", max_items: Optional[int] = None, max_concurrency: int = 5, composite_evaluator: Optional[CompositeEvaluatorFunction] = None, @@ -871,7 +871,7 @@ async def run_async( evaluators: List of evaluation functions to run on each item. filter: JSON filter string for querying items. fetch_batch_size: Number of items to fetch per API call. - fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'. + fetch_trace_fields: Comma-separated list of fields to include when fetching traces. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. Only relevant if scope is 'traces'. Default: 'io' max_items: Maximum number of items to process (None = all). max_concurrency: Maximum number of concurrent evaluations. composite_evaluator: Optional function to create composite scores. diff --git a/langfuse/experiment.py b/langfuse/experiment.py index 00c54fe74..79a3bcdad 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -8,7 +8,6 @@ import asyncio import logging from typing import ( - TYPE_CHECKING, Any, Awaitable, Dict, @@ -19,10 +18,7 @@ Union, ) -from langfuse.api import ScoreDataType - -if TYPE_CHECKING: - from langfuse._client.datasets import DatasetItemClient +from langfuse.api import DatasetItem, ScoreDataType class LocalExperimentItem(TypedDict, total=False): @@ -76,20 +72,20 @@ class LocalExperimentItem(TypedDict, total=False): metadata: Optional[Dict[str, Any]] -ExperimentItem = Union[LocalExperimentItem, "DatasetItemClient"] +ExperimentItem = Union[LocalExperimentItem, DatasetItem] """Type alias for items that can be processed in experiments. Can be either: - LocalExperimentItem: Dict-like items with 'input', 'expected_output', 'metadata' keys -- DatasetItemClient: Items from Langfuse datasets with .input, .expected_output, .metadata attributes +- DatasetItem: Items from Langfuse datasets with .input, .expected_output, .metadata attributes """ -ExperimentData = Union[List[LocalExperimentItem], List["DatasetItemClient"]] +ExperimentData = Union[List[LocalExperimentItem], List[DatasetItem]] """Type alias for experiment datasets. Represents the collection of items to process in an experiment. Can be either: - List[LocalExperimentItem]: Local data items as dictionaries -- List[DatasetItemClient]: Items from a Langfuse dataset (typically from dataset.items) +- List[DatasetItem]: Items from a Langfuse dataset (typically from dataset.items) """ @@ -222,7 +218,7 @@ class ExperimentItemResult: Attributes: item: The original experiment item that was processed. Can be either a dictionary with 'input', 'expected_output', and 'metadata' keys, - or a DatasetItemClient from Langfuse datasets. + or a DatasetItem from Langfuse datasets. output: The actual output produced by the task function for this item. Can be any type depending on what your task function returns. evaluations: List of evaluation results for this item. Each evaluation diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index a2e9816da..a1ed9b28d 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -16,7 +16,9 @@ import pydantic from opentelemetry import context, trace from opentelemetry.context import _RUNTIME_CONTEXT +from opentelemetry.util._decorator import _AgnosticContextManager +from langfuse import propagate_attributes from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse._client.client import Langfuse from langfuse._client.get_client import get_client @@ -96,14 +98,12 @@ def __init__( self, *, public_key: Optional[str] = None, - update_trace: bool = False, trace_context: Optional[TraceContext] = None, ) -> None: """Initialize the LangchainCallbackHandler. Args: public_key: Optional Langfuse public key. If not provided, will use the default client configuration. - update_trace: Whether to update the Langfuse trace with the chains input / output / metadata / name. Defaults to False. trace_context: Optional context for connecting to an existing trace (distributed tracing) or setting a custom trace id for the root LangChain run. Pass a `TraceContext` dict, e.g. `{"trace_id": ""}` (and optionally `{"parent_span_id": ""}`) to link @@ -118,10 +118,8 @@ def __init__( handler = CallbackHandler(trace_context={"trace_id": "my-trace-id"}) ``` """ - self.client = get_client(public_key=public_key) - self.run_inline = True - - self.runs: Dict[ + self._langfuse_client = get_client(public_key=public_key) + self._runs: Dict[ UUID, Union[ LangfuseSpan, @@ -132,14 +130,14 @@ def __init__( LangfuseRetriever, ], ] = {} + self._context_tokens: Dict[UUID, Token] = {} + self._prompt_to_parent_run_map: Dict[UUID, Any] = {} + self._updated_completion_start_time_memo: Set[UUID] = set() + self._propagation_context_manager: Optional[_AgnosticContextManager] = None + self._trace_context = trace_context self._child_to_parent_run_id_map: Dict[UUID, Optional[UUID]] = {} - self.context_tokens: Dict[UUID, Token] = {} - self.prompt_to_parent_run_map: Dict[UUID, Any] = {} - self.updated_completion_start_time_memo: Set[UUID] = set() self.last_trace_id: Optional[str] = None - self.update_trace = update_trace - self.trace_context = trace_context def on_llm_new_token( self, @@ -154,14 +152,14 @@ def on_llm_new_token( f"on llm new token: run_id: {run_id} parent_run_id: {parent_run_id}" ) if ( - run_id in self.runs - and isinstance(self.runs[run_id], LangfuseGeneration) - and run_id not in self.updated_completion_start_time_memo + run_id in self._runs + and isinstance(self._runs[run_id], LangfuseGeneration) + and run_id not in self._updated_completion_start_time_memo ): - current_generation = cast(LangfuseGeneration, self.runs[run_id]) + current_generation = cast(LangfuseGeneration, self._runs[run_id]) current_generation.update(completion_start_time=_get_timestamp()) - self.updated_completion_start_time_memo.add(run_id) + self._updated_completion_start_time_memo.add(run_id) def _get_observation_type_from_serialized( self, serialized: Optional[Dict[str, Any]], callback_type: str, **kwargs: Any @@ -268,12 +266,14 @@ def on_retriever_error( except Exception as e: langfuse_logger.exception(e) - def _parse_langfuse_trace_attributes_from_metadata( - self, - metadata: Optional[Dict[str, Any]], + def _parse_langfuse_trace_attributes( + self, *, metadata: Optional[Dict[str, Any]], tags: Optional[List[str]] ) -> Dict[str, Any]: attributes: Dict[str, Any] = {} + if metadata is None and tags is not None: + return {"tags": tags} + if metadata is None: return attributes @@ -287,8 +287,19 @@ def _parse_langfuse_trace_attributes_from_metadata( ): attributes["user_id"] = metadata["langfuse_user_id"] - if "langfuse_tags" in metadata and isinstance(metadata["langfuse_tags"], list): - attributes["tags"] = [str(tag) for tag in metadata["langfuse_tags"]] + if tags is not None or ( + "langfuse_tags" in metadata and isinstance(metadata["langfuse_tags"], list) + ): + langfuse_tags = ( + metadata["langfuse_tags"] + if "langfuse_tags" in metadata + and isinstance(metadata["langfuse_tags"], list) + else [] + ) + merged_tags = list(set(langfuse_tags) | set(tags or [])) + attributes["tags"] = [str(tag) for tag in set(merged_tags)] + + attributes["metadata"] = _strip_langfuse_keys_from_dict(metadata, False) return attributes @@ -321,10 +332,25 @@ def on_chain_start( serialized, "chain", **kwargs ) + # Handle trace attribute propagation at the root of the chain + if parent_run_id is None: + parsed_trace_attributes = self._parse_langfuse_trace_attributes( + metadata=metadata, tags=tags + ) + + self._propagation_context_manager = propagate_attributes( + user_id=parsed_trace_attributes.get("user_id", None), + session_id=parsed_trace_attributes.get("session_id", None), + tags=parsed_trace_attributes.get("tags", None), + metadata=parsed_trace_attributes.get("metadata", None), + ) + + self._propagation_context_manager.__enter__() + obs = self._get_parent_observation(parent_run_id) if isinstance(obs, Langfuse): span = obs.start_observation( - trace_context=self.trace_context, + trace_context=self._trace_context, name=span_name, as_type=observation_type, metadata=span_metadata, @@ -348,24 +374,7 @@ def on_chain_start( self._attach_observation(run_id, span) - if parent_run_id is None: - span.update_trace( - **( - cast( - Any, - { - "input": inputs, - "name": span_name, - "metadata": span_metadata, - }, - ) - if self.update_trace - else {} - ), - **self._parse_langfuse_trace_attributes_from_metadata(metadata), - ) - - self.last_trace_id = self.runs[run_id].trace_id + self.last_trace_id = self._runs[run_id].trace_id except Exception as e: langfuse_logger.exception(e) @@ -388,17 +397,17 @@ def _register_langfuse_prompt( langfuse_prompt = metadata and metadata.get("langfuse_prompt", None) if langfuse_prompt: - self.prompt_to_parent_run_map[parent_run_id] = langfuse_prompt + self._prompt_to_parent_run_map[parent_run_id] = langfuse_prompt # If we have a registered prompt that has not been linked to a generation yet, we need to allow _children_ of that chain to link to it. # Otherwise, we only allow generations on the same level of the prompt rendering to be linked, not if they are nested. - elif parent_run_id in self.prompt_to_parent_run_map: - registered_prompt = self.prompt_to_parent_run_map[parent_run_id] - self.prompt_to_parent_run_map[run_id] = registered_prompt + elif parent_run_id in self._prompt_to_parent_run_map: + registered_prompt = self._prompt_to_parent_run_map[parent_run_id] + self._prompt_to_parent_run_map[run_id] = registered_prompt def _deregister_langfuse_prompt(self, run_id: Optional[UUID]) -> None: - if run_id is not None and run_id in self.prompt_to_parent_run_map: - del self.prompt_to_parent_run_map[run_id] + if run_id is not None and run_id in self._prompt_to_parent_run_map: + del self._prompt_to_parent_run_map[run_id] def _get_parent_observation( self, parent_run_id: Optional[UUID] @@ -411,10 +420,10 @@ def _get_parent_observation( LangfuseSpan, LangfuseTool, ]: - if parent_run_id and parent_run_id in self.runs: - return self.runs[parent_run_id] + if parent_run_id and parent_run_id in self._runs: + return self._runs[parent_run_id] - return self.client + return self._langfuse_client def _attach_observation( self, @@ -431,8 +440,8 @@ def _attach_observation( ctx = trace.set_span_in_context(observation._otel_span) token = context.attach(ctx) - self.runs[run_id] = observation - self.context_tokens[run_id] = token + self._runs[run_id] = observation + self._context_tokens[run_id] = token def _detach_observation( self, run_id: UUID @@ -446,7 +455,7 @@ def _detach_observation( LangfuseTool, ] ]: - token = self.context_tokens.pop(run_id, None) + token = self._context_tokens.pop(run_id, None) if token: try: @@ -471,7 +480,7 @@ def _detach_observation( LangfuseSpan, LangfuseTool, ], - self.runs.pop(run_id, None), + self._runs.pop(run_id, None), ) def on_agent_action( @@ -490,7 +499,7 @@ def on_agent_action( "on_agent_action", run_id, parent_run_id, action=action ) - agent_run = self.runs.get(run_id, None) + agent_run = self._runs.get(run_id, None) if agent_run is not None: agent_run._otel_span.set_attribute( @@ -519,7 +528,7 @@ def on_agent_finish( ) # Langchain is sending same run ID for both agent finish and chain end # handle cleanup of observation in the chain end callback - agent_run = self.runs.get(run_id, None) + agent_run = self._runs.get(run_id, None) if agent_run is not None: agent_run._otel_span.set_attribute( @@ -555,8 +564,11 @@ def on_chain_end( input=kwargs.get("inputs"), ) - if parent_run_id is None and self.update_trace: - span.update_trace(output=outputs, input=kwargs.get("inputs")) + if ( + parent_run_id is None + and self._propagation_context_manager is not None + ): + self._propagation_context_manager.__exit__(None, None, None) span.end() @@ -842,7 +854,7 @@ def __on_llm_action( # Check all parents for registered prompt while current_parent_run_id is not None: - registered_prompt = self.prompt_to_parent_run_map.get( + registered_prompt = self._prompt_to_parent_run_map.get( current_parent_run_id ) @@ -875,7 +887,7 @@ def __on_llm_action( ) # type: ignore self._attach_observation(run_id, generation) - self.last_trace_id = self.runs[run_id].trace_id + self.last_trace_id = self._runs[run_id].trace_id except Exception as e: langfuse_logger.exception(e) @@ -982,7 +994,7 @@ def on_llm_end( langfuse_logger.exception(e) finally: - self.updated_completion_start_time_memo.discard(run_id) + self._updated_completion_start_time_memo.discard(run_id) if parent_run_id is None: self._reset() diff --git a/langfuse/model.py b/langfuse/model.py index 75803d215..69d721597 100644 --- a/langfuse/model.py +++ b/langfuse/model.py @@ -4,52 +4,23 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple, TypedDict, Union -from langfuse.api.resources.commons.types.dataset import ( - Dataset, # noqa: F401 +from langfuse.api import ( + CreateDatasetItemRequest, # noqa + CreateDatasetRequest, # noqa + Dataset, # noqa + DatasetItem, # noqa + DatasetRun, # noqa + DatasetStatus, # noqa + MapValue, # noqa + Observation, # noqa + Prompt, + Prompt_Chat, + Prompt_Text, + TraceWithFullDetails, # noqa ) - -# these imports need to stay here, otherwise imports from our clients wont work -from langfuse.api.resources.commons.types.dataset_item import DatasetItem # noqa: F401 - -# noqa: F401 -from langfuse.api.resources.commons.types.dataset_run import DatasetRun # noqa: F401 - -# noqa: F401 -from langfuse.api.resources.commons.types.dataset_status import ( # noqa: F401 - DatasetStatus, -) -from langfuse.api.resources.commons.types.map_value import MapValue # noqa: F401 -from langfuse.api.resources.commons.types.observation import Observation # noqa: F401 -from langfuse.api.resources.commons.types.trace_with_full_details import ( # noqa: F401 - TraceWithFullDetails, -) - -# noqa: F401 -from langfuse.api.resources.dataset_items.types.create_dataset_item_request import ( # noqa: F401 - CreateDatasetItemRequest, -) -from langfuse.api.resources.dataset_run_items.types.create_dataset_run_item_request import ( # noqa: F401 - CreateDatasetRunItemRequest, -) - -# noqa: F401 -from langfuse.api.resources.datasets.types.create_dataset_request import ( # noqa: F401 - CreateDatasetRequest, -) -from langfuse.api.resources.prompts import Prompt, Prompt_Chat, Prompt_Text from langfuse.logger import langfuse_logger -class ModelUsage(TypedDict): - unit: Optional[str] - input: Optional[int] - output: Optional[int] - total: Optional[int] - input_cost: Optional[float] - output_cost: Optional[float] - total_cost: Optional[float] - - class ChatMessageDict(TypedDict): role: str content: str diff --git a/langfuse/span_filter.py b/langfuse/span_filter.py new file mode 100644 index 000000000..3c086b15e --- /dev/null +++ b/langfuse/span_filter.py @@ -0,0 +1,17 @@ +"""Public span filter helpers for Langfuse OpenTelemetry export control.""" + +from langfuse._client.span_filter import ( + KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES, + is_default_export_span, + is_genai_span, + is_known_llm_instrumentor, + is_langfuse_span, +) + +__all__ = [ + "is_default_export_span", + "is_langfuse_span", + "is_genai_span", + "is_known_llm_instrumentor", + "KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES", +] diff --git a/langfuse/types.py b/langfuse/types.py index 32ebb32d4..067088e40 100644 --- a/langfuse/types.py +++ b/langfuse/types.py @@ -17,16 +17,12 @@ def my_evaluator(*, output: str, **kwargs) -> Evaluation: ``` """ -from datetime import datetime from typing import ( Any, Dict, - List, Literal, - Optional, Protocol, TypedDict, - Union, ) try: @@ -34,43 +30,14 @@ def my_evaluator(*, output: str, **kwargs) -> Evaluation: except ImportError: from typing_extensions import NotRequired -from pydantic import BaseModel -from langfuse.api import MediaContentType, UsageDetails -from langfuse.model import MapValue, ModelUsage, PromptClient +from langfuse.api import MediaContentType SpanLevel = Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"] ScoreDataType = Literal["NUMERIC", "CATEGORICAL", "BOOLEAN"] -class TraceMetadata(TypedDict): - name: Optional[str] - user_id: Optional[str] - session_id: Optional[str] - version: Optional[str] - release: Optional[str] - metadata: Optional[Any] - tags: Optional[List[str]] - public: Optional[bool] - - -class ObservationParams(TraceMetadata, TypedDict): - input: Optional[Any] - output: Optional[Any] - level: Optional[SpanLevel] - status_message: Optional[str] - start_time: Optional[datetime] - end_time: Optional[datetime] - completion_start_time: Optional[datetime] - model: Optional[str] - model_parameters: Optional[Dict[str, MapValue]] - usage: Optional[Union[BaseModel, ModelUsage]] - usage_details: Optional[UsageDetails] - cost_details: Optional[Dict[str, float]] - prompt: Optional[PromptClient] - - class MaskFunction(Protocol): """A function that masks data. @@ -106,8 +73,6 @@ class TraceContext(TypedDict): __all__ = [ "SpanLevel", "ScoreDataType", - "TraceMetadata", - "ObservationParams", "MaskFunction", "ParsedMediaReference", "TraceContext", diff --git a/poetry.lock b/poetry.lock index e83730daf..c0b23faf5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2820,4 +2820,4 @@ cffi = ["cffi (>=1.17,<2.0)", "cffi (>=2.0.0b)"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "a9017df10e02338646323994ef23ff03186333a35da4f3cc4196e244802c2683" +content-hash = "1153d68c8977db835b7425287bacb02a827f6d1a4a259ae6bacf010d2b91bc94" diff --git a/pyproject.toml b/pyproject.toml index 85cbd8c44..b632cdebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" [tool.poetry.dependencies] python = ">=3.10,<4.0" httpx = ">=0.15.4,<1.0" -pydantic = ">=1.10.7, <3.0" +pydantic = "^2" backoff = ">=1.10.0" openai = { version = ">=0.27.8", optional = true } wrapt = "^1.14" @@ -93,12 +93,8 @@ module = [ ignore_missing_imports = true [[tool.mypy.overrides]] -module = [ - "langfuse.api.resources.*", - "langfuse.api.core.*", - "langfuse.api.client" -] -ignore_errors = true +module = "langfuse.api.*" +disable_error_code = ["redundant-cast", "no-untyped-def"] [tool.ruff] target-version = "py310" diff --git a/tests/test_batch_evaluation.py b/tests/test_batch_evaluation.py index 448172a55..27c49acdd 100644 --- a/tests/test_batch_evaluation.py +++ b/tests/test_batch_evaluation.py @@ -10,7 +10,7 @@ import pytest -from langfuse import get_client +from langfuse import get_client, propagate_attributes from langfuse.batch_evaluation import ( BatchEvaluationResult, BatchEvaluationResumeToken, @@ -123,14 +123,14 @@ def test_batch_evaluation_with_filter(langfuse_client): """Test batch evaluation with JSON filter.""" # Create a trace with specific tag unique_tag = f"test-filter-{create_uuid()}" - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=f"filtered-trace-{create_uuid()}" ) as span: - span.update_trace( - input="Filtered test", - output="Filtered output", - tags=[unique_tag], - ) + with propagate_attributes(tags=[unique_tag]): + span.set_trace_io( + input="Filtered test", + output="Filtered output", + ) langfuse_client.flush() time.sleep(3) @@ -751,14 +751,14 @@ def test_pagination_with_max_items(langfuse_client): """Test that max_items limit is respected.""" # Create more traces to ensure we have enough data for i in range(10): - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=f"pagination-test-{create_uuid()}" ) as span: - span.update_trace( - input=f"Input {i}", - output=f"Output {i}", - tags=["pagination_test"], - ) + with propagate_attributes(tags=["pagination_test"]): + span.set_trace_io( + input=f"Input {i}", + output=f"Output {i}", + ) langfuse_client.flush() time.sleep(3) @@ -783,12 +783,14 @@ def test_has_more_items_flag(langfuse_client): # Create enough traces to exceed max_items batch_tag = f"batch-test-{create_uuid()}" for i in range(15): - with langfuse_client.start_as_current_span(name=f"more-items-test-{i}") as span: - span.update_trace( - input=f"Input {i}", - output=f"Output {i}", - tags=[batch_tag], - ) + with langfuse_client.start_as_current_observation( + name=f"more-items-test-{i}" + ) as span: + with propagate_attributes(tags=[batch_tag]): + span.set_trace_io( + input=f"Input {i}", + output=f"Output {i}", + ) langfuse_client.flush() time.sleep(3) diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 2888c0554..adfa003df 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -6,7 +6,7 @@ import pytest -from langfuse import Langfuse +from langfuse import Langfuse, propagate_attributes from langfuse._client.resource_manager import LangfuseResourceManager from langfuse._utils import _get_timestamp from tests.api_wrapper import LangfuseAPI @@ -22,18 +22,18 @@ async def test_concurrency(): async def update_generation(i, langfuse: Langfuse): # Create a new trace with a generation - with langfuse.start_as_current_span(name=f"parent-{i}") as parent_span: - # Set trace name - parent_span.update_trace(name=str(i)) + with langfuse.start_as_current_observation(name=f"parent-{i}"): + with propagate_attributes(trace_name=str(i)): + # Create generation as a child + generation = langfuse.start_observation( + as_type="generation", name=str(i) + ) - # Create generation as a child - generation = langfuse.start_generation(name=str(i)) + # Update generation with metadata + generation.update(metadata={"count": str(i)}) - # Update generation with metadata - generation.update(metadata={"count": str(i)}) - - # End the generation - generation.end() + # End the generation + generation.end() # Create Langfuse client langfuse = Langfuse() @@ -68,11 +68,11 @@ def test_flush(): trace_ids = [] for i in range(2): - # Create spans and set the trace name using update_trace - with langfuse.start_as_current_span(name="span-" + str(i)) as span: - span.update_trace(name=str(i)) - # Store the trace ID for later verification - trace_ids.append(langfuse.get_current_trace_id()) + # Create spans and set the trace name using propagate_attributes + with langfuse.start_as_current_observation(name="span-" + str(i)): + with propagate_attributes(trace_name=str(i)): + # Store the trace ID for later verification + trace_ids.append(langfuse.get_current_trace_id()) # Flush all pending spans to the Langfuse API langfuse.flush() @@ -91,14 +91,14 @@ def test_invalid_score_data_does_not_raise_exception(): langfuse = Langfuse() # Create a span and set trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name="this-is-so-great-new", + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name="this-is-so-great-new", user_id="test", - metadata="test", - ) - # Get trace ID for later use - trace_id = span.trace_id + metadata={"test": "test"}, + ): + # Get trace ID for later use + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -123,14 +123,14 @@ def test_create_numeric_score(): api_wrapper = LangfuseAPI() # Create a span and set trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name="this-is-so-great-new", + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name="this-is-so-great-new", user_id="test", - metadata="test", - ) - # Get trace ID for later use - trace_id = span.trace_id + metadata={"test": "test"}, + ): + # Get trace ID for later use + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -146,8 +146,11 @@ def test_create_numeric_score(): ) # Create a generation in the same trace - generation = langfuse.start_generation( - name="yet another child", metadata="test", trace_context={"trace_id": trace_id} + generation = langfuse.start_observation( + as_type="generation", + name="yet another child", + metadata="test", + trace_context={"trace_id": trace_id}, ) generation.end() @@ -158,10 +161,12 @@ def test_create_numeric_score(): # Retrieve and verify trace = api_wrapper.get_trace(trace_id) - assert trace["scores"][0]["id"] == score_id - assert trace["scores"][0]["value"] == 1 - assert trace["scores"][0]["dataType"] == "NUMERIC" - assert trace["scores"][0]["stringValue"] is None + # Find the score by name (server may transform the ID format) + score = next((s for s in trace["scores"] if s["name"] == "this-is-a-score"), None) + assert score is not None + assert score["value"] == 1 + assert score["dataType"] == "NUMERIC" + assert score["stringValue"] is None def test_create_boolean_score(): @@ -169,14 +174,14 @@ def test_create_boolean_score(): api_wrapper = LangfuseAPI() # Create a span and set trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name="this-is-so-great-new", + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name="this-is-so-great-new", user_id="test", - metadata="test", - ) - # Get trace ID for later use - trace_id = span.trace_id + metadata={"test": "test"}, + ): + # Get trace ID for later use + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -193,8 +198,11 @@ def test_create_boolean_score(): ) # Create a generation in the same trace - generation = langfuse.start_generation( - name="yet another child", metadata="test", trace_context={"trace_id": trace_id} + generation = langfuse.start_observation( + as_type="generation", + name="yet another child", + metadata="test", + trace_context={"trace_id": trace_id}, ) generation.end() @@ -205,10 +213,15 @@ def test_create_boolean_score(): # Retrieve and verify trace = api_wrapper.get_trace(trace_id) - assert trace["scores"][0]["id"] == score_id - assert trace["scores"][0]["dataType"] == "BOOLEAN" - assert trace["scores"][0]["value"] == 1 - assert trace["scores"][0]["stringValue"] == "True" + # Find the score we created by name + created_score = next( + (s for s in trace["scores"] if s["name"] == "this-is-a-score"), None + ) + assert created_score is not None, "Score not found in trace" + assert created_score["id"] == score_id + assert created_score["dataType"] == "BOOLEAN" + assert created_score["value"] == 1 + assert created_score["stringValue"] == "True" def test_create_categorical_score(): @@ -216,14 +229,14 @@ def test_create_categorical_score(): api_wrapper = LangfuseAPI() # Create a span and set trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name="this-is-so-great-new", + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name="this-is-so-great-new", user_id="test", - metadata="test", - ) - # Get trace ID for later use - trace_id = span.trace_id + metadata={"test": "test"}, + ): + # Get trace ID for later use + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -239,8 +252,11 @@ def test_create_categorical_score(): ) # Create a generation in the same trace - generation = langfuse.start_generation( - name="yet another child", metadata="test", trace_context={"trace_id": trace_id} + generation = langfuse.start_observation( + as_type="generation", + name="yet another child", + metadata="test", + trace_context={"trace_id": trace_id}, ) generation.end() @@ -251,10 +267,15 @@ def test_create_categorical_score(): # Retrieve and verify trace = api_wrapper.get_trace(trace_id) - assert trace["scores"][0]["id"] == score_id - assert trace["scores"][0]["dataType"] == "CATEGORICAL" - assert trace["scores"][0]["value"] == 0 - assert trace["scores"][0]["stringValue"] == "high score" + # Find the score we created by name + created_score = next( + (s for s in trace["scores"] if s["name"] == "this-is-a-score"), None + ) + assert created_score is not None, "Score not found in trace" + assert created_score["id"] == score_id + assert created_score["dataType"] == "CATEGORICAL" + assert created_score["value"] == 0 + assert created_score["stringValue"] == "high score" def test_create_score_with_custom_timestamp(): @@ -262,14 +283,14 @@ def test_create_score_with_custom_timestamp(): api_wrapper = LangfuseAPI() # Create a span and set trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name="test-custom-timestamp", + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name="test-custom-timestamp", user_id="test", - metadata="test", - ) - # Get trace ID for later use - trace_id = span.trace_id + metadata={"test": "test"}, + ): + # Get trace ID for later use + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -293,14 +314,19 @@ def test_create_score_with_custom_timestamp(): # Retrieve and verify trace = api_wrapper.get_trace(trace_id) - assert trace["scores"][0]["id"] == score_id - assert trace["scores"][0]["dataType"] == "NUMERIC" - assert trace["scores"][0]["value"] == 0.85 + # Find the score we created by name + created_score = next( + (s for s in trace["scores"] if s["name"] == "custom-timestamp-score"), None + ) + assert created_score is not None, "Score not found in trace" + assert created_score["id"] == score_id + assert created_score["dataType"] == "NUMERIC" + assert created_score["value"] == 0.85 # Verify timestamp is close to our custom timestamp # Parse the timestamp from the API response response_timestamp = datetime.fromisoformat( - trace["scores"][0]["timestamp"].replace("Z", "+00:00") + created_score["timestamp"].replace("Z", "+00:00") ) # Check that the timestamps are within 1 second of each other @@ -315,17 +341,17 @@ def test_create_trace(): langfuse = Langfuse() trace_name = create_uuid() - # Create a span and update the trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name=trace_name, + # Create a span and set the trace properties using propagate_attributes + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name=trace_name, user_id="test", metadata={"key": "value"}, tags=["tag1", "tag2"], - public=True, - ) - # Get trace ID for later verification - trace_id = langfuse.get_current_trace_id() + ): + span.set_trace_as_public() + # Get trace ID for later verification + trace_id = langfuse.get_current_trace_id() # Ensure data is sent to the API langfuse.flush() @@ -349,21 +375,22 @@ def test_create_update_trace(): trace_name = create_uuid() # Create initial span with trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name=trace_name, + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name=trace_name, user_id="test", metadata={"key": "value"}, - public=True, - ) - # Get trace ID for later reference - trace_id = span.trace_id + ): + span.set_trace_as_public() + # Get trace ID for later reference + trace_id = span.trace_id - # Allow a small delay before updating - sleep(1) + # Allow a small delay before updating + sleep(1) - # Update trace properties - span.update_trace(metadata={"key2": "value2"}, public=False) + # Update trace properties with additional metadata + with propagate_attributes(metadata={"key2": "value2"}): + pass # Metadata update only, set_trace_as_public is one-way # Ensure data is sent to the API langfuse.flush() @@ -377,7 +404,7 @@ def test_create_update_trace(): assert trace.user_id == "test" assert trace.metadata["key"] == "value" assert trace.metadata["key2"] == "value2" - assert trace.public is False + assert trace.public is True def test_create_update_current_trace(): @@ -385,25 +412,24 @@ def test_create_update_current_trace(): trace_name = create_uuid() - # Create initial span with trace properties using update_current_trace - with langfuse.start_as_current_span(name="test-span-current") as span: - langfuse.update_current_trace( - name=trace_name, + # Create initial span with trace properties using propagate_attributes and set_current_trace_io + with langfuse.start_as_current_observation(name="test-span-current") as span: + with propagate_attributes( + trace_name=trace_name, user_id="test", metadata={"key": "value"}, - public=True, - input="test_input", - ) - # Get trace ID for later reference - trace_id = span.trace_id + ): + langfuse.set_current_trace_io(input="test_input") + langfuse.set_current_trace_as_public() + # Get trace ID for later reference + trace_id = span.trace_id - # Allow a small delay before updating - sleep(1) + # Allow a small delay before updating + sleep(1) - # Update trace properties using update_current_trace - langfuse.update_current_trace( - metadata={"key2": "value2"}, public=False, version="1.0" - ) + # Update trace properties with additional metadata and version + with propagate_attributes(metadata={"key2": "value2"}, version="1.0"): + pass # Metadata update only, publish is one-way # Ensure data is sent to the API langfuse.flush() @@ -418,7 +444,7 @@ def test_create_update_current_trace(): assert trace.user_id == "test" assert trace.metadata["key"] == "value" assert trace.metadata["key2"] == "value2" - assert trace.public is False + assert trace.public is True assert trace.version == "1.0" assert trace.input == "test_input" @@ -427,7 +453,8 @@ def test_create_generation(): langfuse = Langfuse() # Create a generation using OTEL approach - generation = langfuse.start_generation( + generation = langfuse.start_observation( + as_type="generation", name="query-generation", model="gpt-3.5-turbo-0125", model_parameters={ @@ -526,7 +553,8 @@ def test_create_generation_complex( ): langfuse = Langfuse() - generation = langfuse.start_generation( + generation = langfuse.start_observation( + as_type="generation", name="query-generation", input=[ {"role": "system", "content": "You are a helpful assistant."}, @@ -578,7 +606,7 @@ def test_create_span(): langfuse = Langfuse() # Create span using OTEL-based client - span = langfuse.start_span( + span = langfuse.start_observation( name="span", input={"key": "value"}, output={"key": "value"}, @@ -624,18 +652,17 @@ def test_score_trace(): trace_name = create_uuid() # Create a span and set trace name - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace(name=trace_name) - - # Get trace ID for later verification - trace_id = langfuse.get_current_trace_id() - - # Create score for the trace - langfuse.score_current_trace( - name="valuation", - value=0.5, - comment="This is a comment", - ) + with langfuse.start_as_current_observation(name="test-span"): + with propagate_attributes(trace_name=trace_name): + # Get trace ID for later verification + trace_id = langfuse.get_current_trace_id() + + # Create score for the trace + langfuse.score_current_trace( + name="valuation", + value=0.5, + comment="This is a comment", + ) # Ensure data is sent langfuse.flush() @@ -645,11 +672,10 @@ def test_score_trace(): trace = api_wrapper.get_trace(trace_id) assert trace["name"] == trace_name - assert len(trace["scores"]) == 1 - score = trace["scores"][0] - - assert score["name"] == "valuation" + # Find the score we created by name (server may create additional auto-scores) + score = next((s for s in trace["scores"] if s["name"] == "valuation"), None) + assert score is not None assert score["value"] == 0.5 assert score["comment"] == "This is a comment" assert score["observationId"] is None @@ -662,19 +688,17 @@ def test_score_trace_nested_trace(): trace_name = create_uuid() # Create a trace with span - with langfuse.start_as_current_span(name="test-span") as span: - # Set trace name - span.update_trace(name=trace_name) - - # Score using the span's method for scoring the trace - span.score_trace( - name="valuation", - value=0.5, - comment="This is a comment", - ) - - # Get trace ID for verification - trace_id = span.trace_id + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes(trace_name=trace_name): + # Score using the span's method for scoring the trace + span.score_trace( + name="valuation", + value=0.5, + comment="This is a comment", + ) + + # Get trace ID for verification + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -684,11 +708,10 @@ def test_score_trace_nested_trace(): trace = get_api().trace.get(trace_id) assert trace.name == trace_name - assert len(trace.scores) == 1 - score = trace.scores[0] - - assert score.name == "valuation" + # Find the score we created by name (server may create additional auto-scores) + score = next((s for s in trace.scores if s.name == "valuation"), None) + assert score is not None assert score.value == 0.5 assert score.comment == "This is a comment" assert score.observation_id is None # API returns this field name @@ -701,25 +724,24 @@ def test_score_trace_nested_observation(): trace_name = create_uuid() # Create a parent span and set trace name - with langfuse.start_as_current_span(name="parent-span") as parent_span: - parent_span.update_trace(name=trace_name) - - # Create a child span - child_span = langfuse.start_span(name="span") + with langfuse.start_as_current_observation(name="parent-span") as parent_span: + with propagate_attributes(trace_name=trace_name): + # Create a child span + child_span = langfuse.start_observation(name="span") - # Score the child span - child_span.score( - name="valuation", - value=0.5, - comment="This is a comment", - ) + # Score the child span + child_span.score( + name="valuation", + value=0.5, + comment="This is a comment", + ) - # Get IDs for verification - child_span_id = child_span.id - trace_id = parent_span.trace_id + # Get IDs for verification + child_span_id = child_span.id + trace_id = parent_span.trace_id - # End the child span - child_span.end() + # End the child span + child_span.end() # Ensure data is sent langfuse.flush() @@ -729,11 +751,10 @@ def test_score_trace_nested_observation(): trace = get_api().trace.get(trace_id) assert trace.name == trace_name - assert len(trace.scores) == 1 - - score = trace.scores[0] - assert score.name == "valuation" + # Find the score we created by name (server may create additional auto-scores) + score = next((s for s in trace.scores if s.name == "valuation"), None) + assert score is not None assert score.value == 0.5 assert score.comment == "This is a comment" assert score.observation_id == child_span_id # API returns this field name @@ -745,7 +766,7 @@ def test_score_span(): api_wrapper = LangfuseAPI() # Create a span - span = langfuse.start_span( + span = langfuse.start_observation( name="span", input={"key": "value"}, output={"key": "value"}, @@ -775,12 +796,11 @@ def test_score_span(): # Retrieve and verify trace = api_wrapper.get_trace(trace_id) - assert len(trace["scores"]) == 1 assert len(trace["observations"]) == 1 - score = trace["scores"][0] - - assert score["name"] == "valuation" + # Find the score we created by name (server may create additional auto-scores) + score = next((s for s in trace["scores"] if s["name"] == "valuation"), None) + assert score is not None assert score["value"] == 1 assert score["comment"] == "This is a comment" assert score["observationId"] == span_id @@ -793,17 +813,16 @@ def test_create_trace_and_span(): trace_name = create_uuid() # Create parent span and set trace name - with langfuse.start_as_current_span(name=trace_name) as parent_span: - parent_span.update_trace(name=trace_name) - - # Create a child span - child_span = parent_span.start_span(name="span") + with langfuse.start_as_current_observation(name=trace_name) as parent_span: + with propagate_attributes(trace_name=trace_name): + # Create a child span + child_span = parent_span.start_observation(name="span") - # Get trace ID for verification - trace_id = parent_span.trace_id + # Get trace ID for verification + trace_id = parent_span.trace_id - # End the child span - child_span.end() + # End the child span + child_span.end() # Ensure data is sent langfuse.flush() @@ -831,19 +850,20 @@ def test_create_trace_and_generation(): trace_name = create_uuid() # Create parent span and set trace properties - with langfuse.start_as_current_span(name=trace_name) as parent_span: - parent_span.update_trace( - name=trace_name, input={"key": "value"}, session_id="test-session-id" - ) + with langfuse.start_as_current_observation(name=trace_name) as parent_span: + with propagate_attributes(trace_name=trace_name, session_id="test-session-id"): + parent_span.set_trace_io(input={"key": "value"}) - # Create a generation as child - generation = parent_span.start_generation(name="generation") + # Create a generation as child + generation = parent_span.start_observation( + as_type="generation", name="generation" + ) - # Get IDs for verification - trace_id = parent_span.trace_id + # Get IDs for verification + trace_id = parent_span.trace_id - # End the generation - generation.end() + # End the generation + generation.end() # Ensure data is sent langfuse.flush() @@ -883,8 +903,10 @@ def test_create_generation_and_trace(): trace_context = {"trace_id": langfuse.create_trace_id()} # Create a generation with this context - generation = langfuse.start_generation( - name="generation", trace_context=trace_context + generation = langfuse.start_observation( + as_type="generation", + name="generation", + trace_context=trace_context, ) # Get trace ID for verification @@ -896,10 +918,11 @@ def test_create_generation_and_trace(): sleep(0.1) # Update trace properties in a separate span - with langfuse.start_as_current_span( + with langfuse.start_as_current_observation( name="trace-update", trace_context={"trace_id": trace_id} - ) as span: - span.update_trace(name=trace_name) + ): + with propagate_attributes(trace_name=trace_name): + pass # Ensure data is sent langfuse.flush() @@ -926,7 +949,7 @@ def test_create_span_and_get_observation(): langfuse = Langfuse() # Create span - span = langfuse.start_span(name="span") + span = langfuse.start_observation(name="span") # Get ID for verification span_id = span.id @@ -950,7 +973,7 @@ def test_update_generation(): langfuse = Langfuse() # Create a generation - generation = langfuse.start_generation(name="generation") + generation = langfuse.start_observation(as_type="generation", name="generation") # Update generation with metadata generation.update(metadata={"dict": "value"}) @@ -986,7 +1009,7 @@ def test_update_span(): langfuse = Langfuse() # Create a span - span = langfuse.start_span(name="span") + span = langfuse.start_observation(name="span") # Update the span with metadata span.update(metadata={"dict": "value"}) @@ -1019,7 +1042,7 @@ def test_create_span_and_generation(): langfuse = Langfuse() # Create initial span - span = langfuse.start_span(name="span") + span = langfuse.start_observation(name="span") sleep(0.1) # Get trace ID for later use trace_id = span.trace_id @@ -1027,8 +1050,10 @@ def test_create_span_and_generation(): span.end() # Create generation in the same trace - generation = langfuse.start_generation( - name="generation", trace_context={"trace_id": trace_id} + generation = langfuse.start_observation( + as_type="generation", + name="generation", + trace_context={"trace_id": trace_id}, ) # End the generation generation.end() @@ -1068,17 +1093,17 @@ def test_create_trace_with_id_and_generation(): trace_id = langfuse.create_trace_id() # Create a span in this trace using the trace context - with langfuse.start_as_current_span( + with langfuse.start_as_current_observation( name="parent-span", trace_context={"trace_id": trace_id} - ) as parent_span: - # Set trace name - parent_span.update_trace(name=trace_name) - - # Create a generation in the same trace - generation = parent_span.start_generation(name="generation") + ): + with propagate_attributes(trace_name=trace_name): + # Create a generation in the same trace + generation = langfuse.start_observation( + as_type="generation", name="generation" + ) - # End the generation - generation.end() + # End the generation + generation.end() # Ensure data is sent langfuse.flush() @@ -1106,7 +1131,8 @@ def test_end_generation(): api_wrapper = LangfuseAPI() # Create a generation - generation = langfuse.start_generation( + generation = langfuse.start_observation( + as_type="generation", name="query-generation", model="gpt-3.5-turbo", model_parameters={"max_tokens": "1000", "temperature": "0.9"}, @@ -1148,12 +1174,13 @@ def test_end_generation_with_data(): langfuse = Langfuse() # Create a parent span to set trace properties - with langfuse.start_as_current_span(name="parent-span") as parent_span: + with langfuse.start_as_current_observation(name="parent-span") as parent_span: # Get trace ID trace_id = parent_span.trace_id # Create generation - generation = langfuse.start_generation( + generation = langfuse.start_observation( + as_type="generation", name="query-generation", ) @@ -1223,7 +1250,8 @@ def test_end_generation_with_openai_token_format(): langfuse = Langfuse() # Create a generation - generation = langfuse.start_generation( + generation = langfuse.start_observation( + as_type="generation", name="query-generation", ) @@ -1276,7 +1304,7 @@ def test_end_span(): api_wrapper = LangfuseAPI() # Create a span - span = langfuse.start_span( + span = langfuse.start_observation( name="span", input={"key": "value"}, output={"key": "value"}, @@ -1310,7 +1338,7 @@ def test_end_span_with_data(): langfuse = Langfuse() # Create a span - span = langfuse.start_span( + span = langfuse.start_observation( name="span", input={"key": "value"}, output={"key": "value"}, @@ -1347,7 +1375,8 @@ def test_get_generations(): langfuse = Langfuse() # Create a first generation with random name - generation1 = langfuse.start_generation( + generation1 = langfuse.start_observation( + as_type="generation", name=create_uuid(), ) generation1.end() @@ -1355,7 +1384,8 @@ def test_get_generations(): # Create a second generation with specific name and content generation_name = create_uuid() - generation2 = langfuse.start_generation( + generation2 = langfuse.start_observation( + as_type="generation", name=generation_name, input="great-prompt", output="great-completion", @@ -1384,20 +1414,21 @@ def test_get_generations_by_user(): generation_name = create_uuid() # Create a trace with user ID and a generation as its child - with langfuse.start_as_current_span(name="test-user") as parent_span: - # Set user ID on the trace - parent_span.update_trace(name="test-user", user_id=user_id) - - # Create a generation within the trace - generation = parent_span.start_generation( - name=generation_name, - input="great-prompt", - output="great-completion", - ) - generation.end() + with langfuse.start_as_current_observation(name="test-user"): + with propagate_attributes(trace_name="test-user", user_id=user_id): + # Create a generation within the trace + generation = langfuse.start_observation( + as_type="generation", + name=generation_name, + input="great-prompt", + output="great-completion", + ) + generation.end() # Create another generation that doesn't have this user ID - other_gen = langfuse.start_generation(name="other-generation") + other_gen = langfuse.start_observation( + as_type="generation", name="other-generation" + ) other_gen.end() # Ensure data is sent @@ -1417,7 +1448,7 @@ def test_get_generations_by_user(): def test_kwargs(): langfuse = Langfuse() - # Create kwargs dict with valid parameters for start_span + # Create kwargs dict with valid parameters for start_observation kwargs_dict = { "input": {"key": "value"}, "output": {"key": "value"}, @@ -1425,7 +1456,7 @@ def test_kwargs(): } # Create span with specific kwargs instead of using **kwargs_dict - span = langfuse.start_span( + span = langfuse.start_observation( name="span", input=kwargs_dict["input"], output=kwargs_dict["output"], @@ -1465,23 +1496,23 @@ def test_timezone_awareness(): langfuse = Langfuse() # Create a trace with various observation types - with langfuse.start_as_current_span(name="test") as parent_span: - # Set the trace name - parent_span.update_trace(name="test") - - # Get trace ID for verification - trace_id = parent_span.trace_id - - # Create a span - span = parent_span.start_span(name="span") - span.end() - - # Create a generation - generation = parent_span.start_generation(name="generation") - generation.end() + with langfuse.start_as_current_observation(name="test") as parent_span: + with propagate_attributes(trace_name="test"): + # Get trace ID for verification + trace_id = parent_span.trace_id + + # Create a span + span = parent_span.start_observation(name="span") + span.end() + + # Create a generation + generation = parent_span.start_observation( + as_type="generation", name="generation" + ) + generation.end() # In OTEL-based client, "events" are just spans with minimal duration - event_span = parent_span.start_span(name="event") + event_span = parent_span.start_observation(name="event") event_span.end() # Ensure data is sent @@ -1525,24 +1556,24 @@ def test_timezone_awareness_setting_timestamps(): langfuse = Langfuse() # Create a trace with different observation types - with langfuse.start_as_current_span(name="test") as parent_span: - # Set trace name - parent_span.update_trace(name="test") - - # Get trace ID for verification - trace_id = parent_span.trace_id - - # Create span - span = parent_span.start_span(name="span") - span.end() - - # Create generation - generation = parent_span.start_generation(name="generation") - generation.end() + with langfuse.start_as_current_observation(name="test") as parent_span: + with propagate_attributes(trace_name="test"): + # Get trace ID for verification + trace_id = parent_span.trace_id + + # Create span + span = parent_span.start_observation(name="span") + span.end() + + # Create generation + generation = parent_span.start_observation( + as_type="generation", name="generation" + ) + generation.end() - # Create event-like span - event_span = parent_span.start_span(name="event") - event_span.end() + # Create event-like span + event_span = parent_span.start_observation(name="event") + event_span.end() # Ensure data is sent langfuse.flush() @@ -1577,13 +1608,13 @@ def test_get_trace_by_session_id(): session_id = create_uuid() # Create a trace with a session_id - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace(name=trace_name, session_id=session_id) - # Get trace ID for verification - trace_id = span.trace_id + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes(trace_name=trace_name, session_id=session_id): + # Get trace ID for verification + trace_id = span.trace_id # Create another trace without a session_id - with langfuse.start_as_current_span(name=create_uuid()): + with langfuse.start_as_current_observation(name=create_uuid()): pass # Ensure data is sent @@ -1608,10 +1639,10 @@ def test_fetch_trace(): name = create_uuid() # Create a span and set trace properties - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace(name=name) - # Get trace ID for verification - trace_id = span.trace_id + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes(trace_name=name): + # Get trace ID for verification + trace_id = span.trace_id # Ensure data is sent langfuse.flush() @@ -1636,38 +1667,26 @@ def test_fetch_traces(): trace_ids = [] # First trace - with langfuse.start_as_current_span(name="test1") as span: - span.update_trace( - name=name, - session_id="session-1", - input={"key": "value"}, - output="output-value", - ) - trace_ids.append(span.trace_id) + with langfuse.start_as_current_observation(name="test1") as span: + with propagate_attributes(trace_name=name, session_id="session-1"): + span.set_trace_io(input={"key": "value"}, output="output-value") + trace_ids.append(span.trace_id) sleep(1) # Ensure traces have different timestamps # Second trace - with langfuse.start_as_current_span(name="test2") as span: - span.update_trace( - name=name, - session_id="session-1", - input={"key": "value"}, - output="output-value", - ) - trace_ids.append(span.trace_id) + with langfuse.start_as_current_observation(name="test2") as span: + with propagate_attributes(trace_name=name, session_id="session-1"): + span.set_trace_io(input={"key": "value"}, output="output-value") + trace_ids.append(span.trace_id) sleep(1) # Ensure traces have different timestamps # Third trace - with langfuse.start_as_current_span(name="test3") as span: - span.update_trace( - name=name, - session_id="session-1", - input={"key": "value"}, - output="output-value", - ) - trace_ids.append(span.trace_id) + with langfuse.start_as_current_observation(name="test3") as span: + with propagate_attributes(trace_name=name, session_id="session-1"): + span.set_trace_io(input={"key": "value"}, output="output-value") + trace_ids.append(span.trace_id) # Ensure data is sent langfuse.flush() @@ -1703,17 +1722,16 @@ def test_get_observation(): name = create_uuid() # Create a span and set trace properties - with langfuse.start_as_current_span(name="parent-span") as parent_span: - parent_span.update_trace(name=name) + with langfuse.start_as_current_observation(name="parent-span") as parent_span: + with propagate_attributes(trace_name=name): + # Create a generation as child + generation = parent_span.start_observation(as_type="generation", name=name) - # Create a generation as child - generation = parent_span.start_generation(name=name) + # Get IDs for verification + generation_id = generation.id - # Get IDs for verification - generation_id = generation.id - - # End the generation - generation.end() + # End the generation + generation.end() # Ensure data is sent langfuse.flush() @@ -1735,18 +1753,17 @@ def test_get_observations(): name = create_uuid() # Create a span and set trace properties - with langfuse.start_as_current_span(name="parent-span") as parent_span: - parent_span.update_trace(name=name) - - # Create first generation - gen1 = parent_span.start_generation(name=name) - gen1_id = gen1.id - gen1.end() - - # Create second generation - gen2 = parent_span.start_generation(name=name) - gen2_id = gen2.id - gen2.end() + with langfuse.start_as_current_observation(name="parent-span"): + with propagate_attributes(trace_name=name): + # Create first generation + gen1 = langfuse.start_observation(as_type="generation", name=name) + gen1_id = gen1.id + gen1.end() + + # Create second generation + gen2 = langfuse.start_observation(as_type="generation", name=name) + gen2_id = gen2.id + gen2.end() # Ensure data is sent langfuse.flush() @@ -1813,16 +1830,19 @@ def test_get_sessions(): # Create multiple traces with different session IDs # Create first trace - with langfuse.start_as_current_span(name=name) as span1: - span1.update_trace(name=name, session_id=session1) + with langfuse.start_as_current_observation(name=name): + with propagate_attributes(trace_name=name, session_id=session1): + pass # Create second trace - with langfuse.start_as_current_span(name=name) as span2: - span2.update_trace(name=name, session_id=session2) + with langfuse.start_as_current_observation(name=name): + with propagate_attributes(trace_name=name, session_id=session2): + pass # Create third trace - with langfuse.start_as_current_span(name=name) as span3: - span3.update_trace(name=name, session_id=session3) + with langfuse.start_as_current_observation(name=name): + with propagate_attributes(trace_name=name, session_id=session3): + pass langfuse.flush() @@ -1849,21 +1869,21 @@ def test_create_trace_sampling_zero(): trace_name = create_uuid() # Create a span with trace properties - with sample_rate=0, this will not be sent to the API - with langfuse.start_as_current_span(name="test-span") as span: - span.update_trace( - name=trace_name, + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name=trace_name, user_id="test", metadata={"key": "value"}, tags=["tag1", "tag2"], - public=True, - ) - # Get trace ID for verification - trace_id = span.trace_id - - # Add a score and a child generation - langfuse.score_current_trace(name="score", value=0.5) - generation = span.start_generation(name="generation") - generation.end() + ): + span.set_trace_as_public() + # Get trace ID for verification + trace_id = span.trace_id + + # Add a score and a child generation + langfuse.score_current_trace(name="score", value=0.5) + generation = span.start_observation(as_type="generation", name="generation") + generation.end() # Ensure data is sent, but should be dropped due to sampling langfuse.flush() @@ -1893,22 +1913,29 @@ def mask_func(data): api_wrapper = LangfuseAPI() # Create a root span with trace properties - with langfuse.start_as_current_span(name="test-span") as root_span: - root_span.update_trace(name="test_trace", input={"sensitive": "data"}) - # Get trace ID for later use - trace_id = root_span.trace_id - # Add output to the trace - root_span.update_trace(output={"more": "sensitive"}) - - # Create a generation as child - gen = root_span.start_generation(name="test_gen", input={"prompt": "secret"}) - gen.update(output="new_confidential") - gen.end() - - # Create a span as child - sub_span = root_span.start_span(name="test_span", input={"data": "private"}) - sub_span.update(output="new_classified") - sub_span.end() + with langfuse.start_as_current_observation(name="test-span") as root_span: + with propagate_attributes(trace_name="test_trace"): + root_span.set_trace_io(input={"sensitive": "data"}) + # Get trace ID for later use + trace_id = root_span.trace_id + # Add output to the trace + root_span.set_trace_io(output={"more": "sensitive"}) + + # Create a generation as child + gen = root_span.start_observation( + as_type="generation", + name="test_gen", + input={"prompt": "secret"}, + ) + gen.update(output="new_confidential") + gen.end() + + # Create a span as child + sub_span = root_span.start_observation( + name="test_span", input={"data": "private"} + ) + sub_span.update(output="new_classified") + sub_span.end() # Ensure data is sent langfuse.flush() @@ -1934,12 +1961,13 @@ def mask_func(data): assert fetched_span["output"] == "MASKED" # Create a root span with trace properties - with langfuse.start_as_current_span(name="test-span") as root_span: - root_span.update_trace(name="test_trace", input={"should_raise": "data"}) - # Get trace ID for later use - trace_id = root_span.trace_id - # Add output to the trace - root_span.update_trace(output={"should_raise": "sensitive"}) + with langfuse.start_as_current_observation(name="test-span") as root_span: + with propagate_attributes(trace_name="test_trace"): + root_span.set_trace_io(input={"should_raise": "data"}) + # Get trace ID for later use + trace_id = root_span.trace_id + # Add output to the trace + root_span.set_trace_io(output={"should_raise": "sensitive"}) # Ensure data is sent langfuse.flush() @@ -1963,10 +1991,11 @@ def test_generate_trace_id(): trace_id = langfuse.create_trace_id() # Create a trace with the specific ID using trace_context - with langfuse.start_as_current_span( + with langfuse.start_as_current_observation( name="test-span", trace_context={"trace_id": trace_id} - ) as span: - span.update_trace(name="test_trace") + ): + with propagate_attributes(trace_name="test_trace"): + pass langfuse.flush() @@ -1979,7 +2008,7 @@ def test_generate_trace_id(): def test_generate_trace_url_client_disabled(): langfuse = Langfuse(tracing_enabled=False) - with langfuse.start_as_current_span( + with langfuse.start_as_current_observation( name="test-span", ): # The trace URL should be None because the client is disabled @@ -2005,15 +2034,15 @@ def test_start_as_current_observation_types(): "guardrail", ] - with langfuse.start_as_current_span(name="parent") as parent_span: - parent_span.update_trace(name="observation-types-test") - trace_id = parent_span.trace_id + with langfuse.start_as_current_observation(name="parent") as parent_span: + with propagate_attributes(trace_name="observation-types-test"): + trace_id = parent_span.trace_id - for obs_type in observation_types: - with parent_span.start_as_current_observation( - name=f"test-{obs_type}", as_type=obs_type - ): - pass + for obs_type in observation_types: + with parent_span.start_as_current_observation( + name=f"test-{obs_type}", as_type=obs_type + ): + pass langfuse.flush() sleep(2) @@ -2056,41 +2085,41 @@ def test_that_generation_like_properties_are_actually_created(): test_usage_details = {"prompt_tokens": 10, "completion_tokens": 20} test_cost_details = {"input": 0.01, "output": 0.02, "total": 0.03} - with langfuse.start_as_current_span(name="parent") as parent_span: - parent_span.update_trace(name="generation-properties-test") - trace_id = parent_span.trace_id - - for obs_type in generation_like_types: - with parent_span.start_as_current_observation( - name=f"test-{obs_type}", - as_type=obs_type, - model=test_model, - completion_start_time=test_completion_start_time, - model_parameters=test_model_parameters, - usage_details=test_usage_details, - cost_details=test_cost_details, - ) as obs: - # Verify the properties are accessible on the observation object - if hasattr(obs, "model"): - assert obs.model == test_model, ( - f"{obs_type} should have model property" - ) - if hasattr(obs, "completion_start_time"): - assert obs.completion_start_time == test_completion_start_time, ( - f"{obs_type} should have completion_start_time property" - ) - if hasattr(obs, "model_parameters"): - assert obs.model_parameters == test_model_parameters, ( - f"{obs_type} should have model_parameters property" - ) - if hasattr(obs, "usage_details"): - assert obs.usage_details == test_usage_details, ( - f"{obs_type} should have usage_details property" - ) - if hasattr(obs, "cost_details"): - assert obs.cost_details == test_cost_details, ( - f"{obs_type} should have cost_details property" - ) + with langfuse.start_as_current_observation(name="parent") as parent_span: + with propagate_attributes(trace_name="generation-properties-test"): + trace_id = parent_span.trace_id + + for obs_type in generation_like_types: + with parent_span.start_as_current_observation( + name=f"test-{obs_type}", + as_type=obs_type, + model=test_model, + completion_start_time=test_completion_start_time, + model_parameters=test_model_parameters, + usage_details=test_usage_details, + cost_details=test_cost_details, + ) as obs: + # Verify the properties are accessible on the observation object + if hasattr(obs, "model"): + assert obs.model == test_model, ( + f"{obs_type} should have model property" + ) + if hasattr(obs, "completion_start_time"): + assert ( + obs.completion_start_time == test_completion_start_time + ), f"{obs_type} should have completion_start_time property" + if hasattr(obs, "model_parameters"): + assert obs.model_parameters == test_model_parameters, ( + f"{obs_type} should have model_parameters property" + ) + if hasattr(obs, "usage_details"): + assert obs.usage_details == test_usage_details, ( + f"{obs_type} should have usage_details property" + ) + if hasattr(obs, "cost_details"): + assert obs.cost_details == test_cost_details, ( + f"{obs_type} should have cost_details property" + ) langfuse.flush() diff --git a/tests/test_datasets.py b/tests/test_datasets.py index f20c76f24..7cbbed817 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -1,17 +1,9 @@ -import json import time -from concurrent.futures import ThreadPoolExecutor from datetime import timedelta -from typing import Sequence -from langchain_core.prompts import PromptTemplate -from langchain_openai import OpenAI - -from langfuse import Langfuse, observe -from langfuse.api.resources.commons.types.dataset_status import DatasetStatus -from langfuse.api.resources.commons.types.observation import Observation -from langfuse.langchain import CallbackHandler -from tests.utils import create_uuid, get_api +from langfuse import Langfuse +from langfuse.api import DatasetStatus +from tests.utils import create_uuid def test_create_and_get_dataset(): @@ -37,7 +29,7 @@ def test_create_dataset_item(): name = create_uuid() langfuse.create_dataset(name=name) - generation = langfuse.start_generation(name="test").end() + generation = langfuse.start_observation(as_type="generation", name="test").end() langfuse.flush() input = {"input": "Hello World"} @@ -142,392 +134,37 @@ def test_upsert_and_get_dataset_item(): assert get_new_item.status == DatasetStatus.ARCHIVED -def test_dataset_run_with_metadata_and_description(): - langfuse = Langfuse(debug=False) - - dataset_name = create_uuid() - langfuse.create_dataset(name=dataset_name) - - input = {"input": "Hello World"} - langfuse.create_dataset_item(dataset_name=dataset_name, input=input) - - dataset = langfuse.get_dataset(dataset_name) - assert len(dataset.items) == 1 - assert dataset.items[0].input == input - - run_name = create_uuid() - - for item in dataset.items: - # Use run() with metadata and description - with item.run( - run_name=run_name, - run_metadata={"key": "value"}, - run_description="This is a test run", - ) as span: - span.update_trace(name=run_name, metadata={"key": "value"}) - - langfuse.flush() - time.sleep(1) # Give API time to process - - # Get trace using the API directly - api = get_api() - response = api.trace.list(name=run_name) - - assert response.data, "No traces found for the dataset run" - trace = api.trace.get(response.data[0].id) - - assert trace.name == run_name - assert trace.metadata is not None - assert "key" in trace.metadata - assert trace.metadata["key"] == "value" - assert trace.id is not None - - -def test_get_dataset_runs(): +def test_run_experiment(): + """Test running an experiment on a dataset using run_experiment().""" langfuse = Langfuse(debug=False) dataset_name = create_uuid() langfuse.create_dataset(name=dataset_name) - input = {"input": "Hello World"} - langfuse.create_dataset_item(dataset_name=dataset_name, input=input) + input_data = {"input": "Hello World"} + langfuse.create_dataset_item(dataset_name=dataset_name, input=input_data) dataset = langfuse.get_dataset(dataset_name) assert len(dataset.items) == 1 - assert dataset.items[0].input == input - - run_name_1 = create_uuid() - - for item in dataset.items: - with item.run( - run_name=run_name_1, - run_metadata={"key": "value"}, - run_description="This is a test run", - ): - pass - - langfuse.flush() - time.sleep(1) # Give API time to process - - run_name_2 = create_uuid() - - for item in dataset.items: - with item.run( - run_name=run_name_2, - run_metadata={"key": "value"}, - run_description="This is a test run", - ): - pass - - langfuse.flush() - time.sleep(1) # Give API time to process - runs = langfuse.get_dataset_runs(dataset_name=dataset_name) - - assert len(runs.data) == 2 - assert runs.data[0].name == run_name_2 - assert runs.data[0].metadata == {"key": "value"} - assert runs.data[0].description == "This is a test run" - assert runs.data[1].name == run_name_1 - assert runs.meta.total_items == 2 - assert runs.meta.total_pages == 1 - assert runs.meta.page == 1 - assert runs.meta.limit == 50 - - -def test_langchain_dataset(): - langfuse = Langfuse(debug=False) - dataset_name = create_uuid() - langfuse.create_dataset(name=dataset_name) - - input = json.dumps({"input": "Hello World"}) - langfuse.create_dataset_item(dataset_name=dataset_name, input=input) - - dataset = langfuse.get_dataset(dataset_name) - - run_name = create_uuid() - - dataset_item_id = None - final_trace_id = None - - for item in dataset.items: - # Run item with the Langchain model inside the context manager - with item.run(run_name=run_name) as span: - dataset_item_id = item.id - final_trace_id = span.trace_id - - llm = OpenAI() - template = """You are a playwright. Given the title of play, it is your job to write a synopsis for that title. - Title: {title} - Playwright: This is a synopsis for the above play:""" - - prompt_template = PromptTemplate( - input_variables=["title"], template=template - ) - chain = prompt_template | llm - - # Create an OpenAI generation as a nested - handler = CallbackHandler() - chain.invoke( - "Tragedy at sunset on the beach", config={"callbacks": [handler]} - ) - - langfuse.flush() - time.sleep(1) # Give API time to process - - # Get the trace directly - api = get_api() - assert final_trace_id is not None, "No trace ID was created" - trace = api.trace.get(final_trace_id) - - assert trace is not None - assert len(trace.observations) >= 1 - - # Update the sorted_dependencies function to handle ObservationsView - def sorted_dependencies_from_trace(trace): - parent_to_observation = {} - for obs in trace.observations: - # Filter out the generation that might leak in due to the monkey patching OpenAI integration - # that might have run in the previous test suite. TODO: fix this hack - if obs.name == "OpenAI-generation": - continue - - parent_to_observation[obs.parent_observation_id] = obs - - # Start with the root observation (parent_observation_id is None) - if None not in parent_to_observation: - return [] - - current_observation = parent_to_observation[None] - dependencies = [current_observation] - - next_parent_id = current_observation.id - while next_parent_id in parent_to_observation: - current_observation = parent_to_observation[next_parent_id] - dependencies.append(current_observation) - next_parent_id = current_observation.id - - return dependencies - - sorted_observations = sorted_dependencies_from_trace(trace) - - if len(sorted_observations) >= 2: - assert sorted_observations[0].id == sorted_observations[1].parent_observation_id - assert sorted_observations[0].parent_observation_id is None - - assert trace.name == f"Dataset run: {run_name}" - assert trace.metadata["dataset_item_id"] == dataset_item_id - assert trace.metadata["run_name"] == run_name - assert trace.metadata["dataset_id"] == dataset.id - - if len(sorted_observations) >= 2: - assert sorted_observations[1].name == "RunnableSequence" - assert sorted_observations[1].type == "CHAIN" - assert sorted_observations[1].input is not None - assert sorted_observations[1].output is not None - assert sorted_observations[1].input != "" - assert sorted_observations[1].output != "" - - -def sorted_dependencies( - observations: Sequence[Observation], -): - # observations have an id and a parent_observation_id. Return a sorted list starting with the root observation where the parent_observation_id is None - parent_to_observation = {obs.parent_observation_id: obs for obs in observations} - - if None not in parent_to_observation: - return [] - - # Start with the root observation (parent_observation_id is None) - current_observation = parent_to_observation[None] - dependencies = [current_observation] - - next_parent_id = current_observation.id - while next_parent_id in parent_to_observation: - current_observation = parent_to_observation[next_parent_id] - dependencies.append(current_observation) - next_parent_id = current_observation.id - - return dependencies - - -def test_observe_dataset_run(): - # Create dataset - langfuse = Langfuse() - dataset_name = create_uuid() - langfuse.create_dataset(name=dataset_name) - - items_data = [] - num_items = 3 - - for i in range(num_items): - trace_id = langfuse.create_trace_id() - dataset_item_input = "Hello World " + str(i) - langfuse.create_dataset_item( - dataset_name=dataset_name, input=dataset_item_input - ) - - items_data.append((dataset_item_input, trace_id)) - - dataset = langfuse.get_dataset(dataset_name) - assert len(dataset.items) == num_items + assert dataset.items[0].input == input_data run_name = create_uuid() - @observe() - def run_llm_app_on_dataset_item(input): - return input - - def wrapperFunc(input): - return run_llm_app_on_dataset_item(input) - - def execute_dataset_item(item, run_name): - with item.run(run_name=run_name) as span: - trace_id = span.trace_id - span.update_trace( - name="run_llm_app_on_dataset_item", - input={"args": [item.input]}, - output=item.input, - ) - wrapperFunc(item.input) - return trace_id - - # Execute dataset items in parallel - items = dataset.items[::-1] # Reverse order to reflect input order - trace_ids = [] - - with ThreadPoolExecutor() as executor: - for item in items: - result = executor.submit( - execute_dataset_item, - item, - run_name=run_name, - ) - trace_ids.append(result.result()) - - langfuse.flush() - time.sleep(1) # Give API time to process - - # Verify each trace individually - api = get_api() - for i, trace_id in enumerate(trace_ids): - trace = api.trace.get(trace_id) - assert trace is not None - assert trace.name == "run_llm_app_on_dataset_item" - assert trace.output is not None - # Verify the input was properly captured - expected_input = dataset.items[len(dataset.items) - 1 - i].input - assert trace.input is not None - assert "args" in trace.input - assert trace.input["args"][0] == expected_input - assert trace.output == expected_input - - -def test_get_dataset_with_folder_name(): - """Test that get_dataset works with folder-format names containing slashes.""" - langfuse = Langfuse(debug=False) - - # Create a dataset with slashes in the name (folder format) - folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" - langfuse.create_dataset(name=folder_name) - - # Fetch the dataset using the wrapper method - dataset = langfuse.get_dataset(folder_name) - assert dataset.name == folder_name - assert "/" in dataset.name # Verify slashes are preserved - - -def test_get_dataset_runs_with_folder_name(): - """Test that get_dataset_runs works with folder-format dataset names.""" - langfuse = Langfuse(debug=False) - - # Create a dataset with slashes in the name - folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" - langfuse.create_dataset(name=folder_name) - - # Create a dataset item - langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) - dataset = langfuse.get_dataset(folder_name) - assert len(dataset.items) == 1 - - # Create a run - run_name = f"run-{create_uuid()[:8]}" - for item in dataset.items: - with item.run(run_name=run_name): - pass - - langfuse.flush() - time.sleep(1) # Give API time to process - - # Fetch runs using the new wrapper method - runs = langfuse.get_dataset_runs(dataset_name=folder_name) - assert len(runs.data) == 1 - assert runs.data[0].name == run_name - - -def test_get_dataset_run_with_folder_names(): - """Test that get_dataset_run works with folder-format dataset and run names.""" - langfuse = Langfuse(debug=False) - - # Create a dataset with slashes in the name - folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" - langfuse.create_dataset(name=folder_name) - - # Create a dataset item - langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) - dataset = langfuse.get_dataset(folder_name) - assert len(dataset.items) == 1 - - # Create a run with slashes in the name - run_name = f"run/nested/{create_uuid()[:8]}" - for item in dataset.items: - with item.run(run_name=run_name, run_metadata={"key": "value"}): - pass - - langfuse.flush() - time.sleep(1) # Give API time to process - - # Fetch the specific run using the new wrapper method - run = langfuse.get_dataset_run(dataset_name=folder_name, run_name=run_name) - assert run.name == run_name - assert run.dataset_name == folder_name - assert run.metadata == {"key": "value"} - assert "/" in run_name # Verify slashes are preserved in run name - - -def test_delete_dataset_run_with_folder_names(): - """Test that delete_dataset_run works with folder-format dataset and run names.""" - langfuse = Langfuse(debug=False) - - # Create a dataset with slashes in the name - folder_name = f"folder/subfolder/dataset-{create_uuid()[:8]}" - langfuse.create_dataset(name=folder_name) - - # Create a dataset item - langfuse.create_dataset_item(dataset_name=folder_name, input={"test": "data"}) - dataset = langfuse.get_dataset(folder_name) + def simple_task(*, item, **kwargs): + return f"Processed: {item.input}" - # Create a run with slashes in the name - run_name = f"run/to/delete/{create_uuid()[:8]}" - for item in dataset.items: - with item.run(run_name=run_name): - pass + result = dataset.run_experiment( + name=run_name, + task=simple_task, + metadata={"key": "value"}, + ) langfuse.flush() time.sleep(1) # Give API time to process - # Verify the run exists - runs_before = langfuse.get_dataset_runs(dataset_name=folder_name) - assert len(runs_before.data) == 1 - - # Delete the run using the new wrapper method - result = langfuse.delete_dataset_run(dataset_name=folder_name, run_name=run_name) - assert result.message is not None - - time.sleep(1) # Give API time to process deletion - - # Verify the run is deleted - runs_after = langfuse.get_dataset_runs(dataset_name=folder_name) - assert len(runs_after.data) == 0 + assert result is not None + assert len(result.item_results) == 1 + assert result.item_results[0].output == f"Processed: {input_data}" def test_get_dataset_with_version(): diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 0c82c1a6f..c6ed42594 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -11,7 +11,7 @@ from langchain_openai import ChatOpenAI from opentelemetry import trace -from langfuse import Langfuse, get_client, observe +from langfuse import Langfuse, get_client, observe, propagate_attributes from langfuse._client.environment_variables import LANGFUSE_PUBLIC_KEY from langfuse._client.resource_manager import LangfuseResourceManager from langfuse.langchain import CallbackHandler @@ -47,11 +47,10 @@ def level_3_function(): output="mock_output", ) langfuse.update_current_generation(version="version-1") - langfuse.update_current_trace(session_id=mock_session_id, name=mock_name) - - langfuse.update_current_trace( - user_id="user_id", - ) + with propagate_attributes( + session_id=mock_session_id, trace_name=mock_name, user_id="user_id" + ): + pass return "level_3" @@ -130,11 +129,10 @@ def level_3_function(): ) langfuse.update_current_generation(version="version-1") - langfuse.update_current_trace(session_id=mock_session_id, name=mock_name) - - langfuse.update_current_trace( - user_id="user_id", - ) + with propagate_attributes( + session_id=mock_session_id, trace_name=mock_name, user_id="user_id" + ): + pass return "level_3" @@ -211,7 +209,8 @@ def level_3_function(): usage_details={"input": 150, "output": 50, "total": 300}, model="gpt-3.5-turbo", ) - langfuse.update_current_trace(session_id=mock_session_id, name=mock_name) + with propagate_attributes(session_id=mock_session_id, trace_name=mock_name): + pass raise ValueError("Mock exception") @@ -284,7 +283,8 @@ def level_3_function(): usage_details={"input": 150, "output": 50, "total": 300}, model="gpt-3.5-turbo", ) - langfuse.update_current_trace(name=mock_name, session_id=mock_session_id) + with propagate_attributes(trace_name=mock_name, session_id=mock_session_id): + pass return "level_3" @@ -385,8 +385,6 @@ def langchain_operations(*args, **kwargs): def level_3_function(*args, **kwargs): langfuse.update_current_span(metadata=mock_metadata) langfuse.update_current_span(metadata=mock_deep_metadata) - langfuse.update_current_trace(session_id=mock_session_id, name=mock_name) - return langchain_operations(*args, **kwargs) @observe() @@ -399,7 +397,8 @@ def level_2_function(*args, **kwargs): def level_1_function(*args, **kwargs): return level_2_function(*args, **kwargs) - level_1_function(topic="socks", langfuse_trace_id=mock_trace_id) + with propagate_attributes(session_id=mock_session_id, trace_name=mock_name): + level_1_function(topic="socks", langfuse_trace_id=mock_trace_id) langfuse.flush() @@ -482,8 +481,8 @@ def level_2_function(): @observe() def level_1_function(*args, **kwargs): langfuse.score_current_trace(name="test-trace-score", value=3) - langfuse.update_current_trace(name=mock_name) - return level_2_function() + with propagate_attributes(trace_name=mock_name): + return level_2_function() result = level_1_function( *mock_args, **mock_kwargs, langfuse_trace_id=mock_trace_id @@ -633,7 +632,8 @@ def level_3_function(self): output="mock_output", ) - langfuse.update_current_trace(session_id=mock_session_id, name=mock_name) + with propagate_attributes(session_id=mock_session_id, trace_name=mock_name): + pass return "level_3" @@ -834,17 +834,16 @@ async def level_2_function(): stream=True, ) - langfuse.update_current_trace( + with propagate_attributes( session_id=mock_session_id, user_id=mock_user_id, tags=mock_tags, - ) - - async for c in gen: - print(c) + trace_name=mock_name, + ): + async for c in gen: + print(c) - langfuse.update_current_span(metadata=mock_metadata) - langfuse.update_current_trace(name=mock_name) + langfuse.update_current_span(metadata=mock_metadata) return "level_2" @@ -1025,7 +1024,7 @@ def test_media(): @observe() def main(): sleep(1) - langfuse.update_current_trace( + langfuse.set_current_trace_io( input={ "context": { "nested": media, @@ -1036,6 +1035,9 @@ def main(): "nested": media, }, }, + ) + # Note: Trace-level metadata with nested media objects is tested via observation metadata + langfuse.update_current_span( metadata={ "context": { "nested": media, @@ -1057,12 +1059,14 @@ def main(): "@@@langfuseMedia:type=application/pdf|id=" in trace_data.output["context"]["nested"] ) + # Check media in observation metadata + observation = trace_data.observations[0] assert ( "@@@langfuseMedia:type=application/pdf|id=" - in trace_data.metadata["context"]["nested"] + in observation.metadata["context"]["nested"] ) parsed_reference_string = LangfuseMedia.parse_reference_string( - trace_data.metadata["context"]["nested"] + observation.metadata["context"]["nested"] ) assert parsed_reference_string["content_type"] == "application/pdf" assert parsed_reference_string["media_id"] is not None @@ -1075,13 +1079,13 @@ def test_merge_metadata_and_tags(): @observe def nested(): - langfuse.update_current_trace(metadata={"key2": "value2"}, tags=["tag2"]) + with propagate_attributes(metadata={"key2": "value2"}, tags=["tag2"]): + pass @observe def main(): - langfuse.update_current_trace(metadata={"key1": "value1"}, tags=["tag1"]) - - nested() + with propagate_attributes(metadata={"key1": "value1"}, tags=["tag1"]): + nested() main(langfuse_trace_id=mock_trace_id) @@ -1117,7 +1121,8 @@ def level_3_function(): # and NOT need langfuse_public_key parameter langfuse_client = get_client() langfuse_client.update_current_generation(metadata={"level": "3"}) - langfuse_client.update_current_trace(name=mock_name) + with propagate_attributes(trace_name=mock_name): + pass return "level_3" @observe() @@ -1191,11 +1196,11 @@ def level_2_function(): @observe() def level_1_function(*args, **kwargs): - langfuse_client = get_client() - langfuse_client.update_current_trace(name=mock_name) - result = level_2_function() - langfuse_client.update_current_span(metadata={"level": "1"}) - return result + with propagate_attributes(trace_name=mock_name): + result = level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1"}) + return result result = level_1_function( langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key @@ -1253,10 +1258,9 @@ def level_2_function(): @observe() def level_1_function(*args, **kwargs): - langfuse_client = get_client(public_key=primary_public_key) - langfuse_client.update_current_trace(name=mock_name) - level_2_function() - return "level_1" + with propagate_attributes(trace_name=mock_name): + level_2_function() + return "level_1" result = level_1_function( langfuse_trace_id=mock_trace_id, langfuse_public_key=primary_public_key @@ -1303,11 +1307,11 @@ def level_2_function(): @observe() def level_1_function(*args, **kwargs): - langfuse_client = get_client() - langfuse_client.update_current_trace(name=mock_name) - result = level_2_function() - langfuse_client.update_current_span(metadata={"level": "1"}) - return result + with propagate_attributes(trace_name=mock_name): + result = level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1"}) + return result # No langfuse_public_key provided - should use default client result = level_1_function(langfuse_trace_id=mock_trace_id) @@ -1352,7 +1356,8 @@ async def async_level_3_function(): langfuse_client.update_current_generation( metadata={"level": "3", "async": True} ) - langfuse_client.update_current_trace(name=mock_name) + with propagate_attributes(trace_name=mock_name): + pass return "async_level_3" @observe() @@ -1441,11 +1446,13 @@ async def async_level_2_function(): @observe() async def async_level_1_function(*args, **kwargs): # Top-level async function - langfuse_client = get_client() - langfuse_client.update_current_trace(name=mock_name) - result = await async_level_2_function() - langfuse_client.update_current_span(metadata={"level": "1", "type": "async"}) - return result + with propagate_attributes(trace_name=mock_name): + result = await async_level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span( + metadata={"level": "1", "type": "async"} + ) + return result result = await async_level_1_function( langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key @@ -1509,11 +1516,13 @@ async def async_level_2_function(task_id): @observe() async def async_level_1_function(task_id, *args, **kwargs): - langfuse_client = get_client() - langfuse_client.update_current_trace(name=f"{mock_name}_task_{task_id}") - result = await async_level_2_function(task_id) - langfuse_client.update_current_span(metadata={"task_id": task_id, "level": "1"}) - return result + with propagate_attributes(trace_name=f"{mock_name}_task_{task_id}"): + result = await async_level_2_function(task_id) + langfuse_client = get_client() + langfuse_client.update_current_span( + metadata={"task_id": task_id, "level": "1"} + ) + return result # Run two concurrent async tasks with the same public key but different trace contexts task1 = async_level_1_function( @@ -1587,17 +1596,16 @@ async def async_generator_function(): @observe() async def async_consumer_function(): - langfuse_client = get_client() - langfuse_client.update_current_trace(name=mock_name) + with propagate_attributes(trace_name=mock_name): + result = "" + async for item in async_generator_function(): + result += item - result = "" - async for item in async_generator_function(): - result += item - - langfuse_client.update_current_span( - metadata={"type": "consumer", "result": result} - ) - return result + langfuse_client = get_client() + langfuse_client.update_current_span( + metadata={"type": "consumer", "result": result} + ) + return result result = await async_consumer_function( langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key @@ -1643,8 +1651,8 @@ async def async_failing_function(): await asyncio.sleep(0.01) langfuse_client = get_client() langfuse_client.update_current_generation(metadata={"will_fail": True}) - langfuse_client.update_current_trace(name=mock_name) - raise ValueError("Async function failed") + with propagate_attributes(trace_name=mock_name): + raise ValueError("Async function failed") @observe() async def async_caller_function(): diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py deleted file mode 100644 index edda545fd..000000000 --- a/tests/test_deprecation.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Tests for deprecation warnings on deprecated functions.""" - -import warnings -from unittest.mock import patch - -import pytest - -from langfuse import Langfuse - - -class TestDeprecationWarnings: - """Test that deprecated functions emit proper deprecation warnings.""" - - # List of deprecated functions and their expected warning messages. Target is the object they are called on. - DEPRECATED_FUNCTIONS = [ - # on the client: - { - "method": "start_generation", - "target": "client", - "kwargs": {"name": "test_generation"}, - "expected_message": "start_generation is deprecated and will be removed in a future version. Use start_observation(as_type='generation') instead.", - }, - { - "method": "start_as_current_generation", - "target": "client", - "kwargs": {"name": "test_generation"}, - "expected_message": "start_as_current_generation is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='generation') instead.", - }, - # on the span: - { - "method": "start_generation", - "target": "span", - "kwargs": {"name": "test_generation"}, - "expected_message": "start_generation is deprecated and will be removed in a future version. Use start_observation(as_type='generation') instead.", - }, - { - "method": "start_as_current_generation", - "target": "span", - "kwargs": {"name": "test_generation"}, - "expected_message": "start_as_current_generation is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='generation') instead.", - }, - { - "method": "start_as_current_span", - "target": "span", - "kwargs": {"name": "test_span"}, - "expected_message": "start_as_current_span is deprecated and will be removed in a future version. Use start_as_current_observation(as_type='span') instead.", - }, - ] - - @pytest.fixture - def langfuse_client(self): - """Create a Langfuse client for testing.""" - with patch.dict( - "os.environ", - { - "LANGFUSE_PUBLIC_KEY": "test_key", - "LANGFUSE_SECRET_KEY": "test_secret", - "LANGFUSE_BASE_URL": "http://localhost:3000", - }, - ): - return Langfuse() - - @pytest.mark.parametrize("func_info", DEPRECATED_FUNCTIONS) - def test_deprecated_function_warnings(self, langfuse_client, func_info): - """Test that deprecated functions emit proper deprecation warnings.""" - method_name = func_info["method"] - target = func_info["target"] - kwargs = func_info["kwargs"] - expected_message = func_info["expected_message"] - - with warnings.catch_warnings(record=True) as warning_list: - warnings.simplefilter("always") - - try: - if target == "client": - # Test deprecated methods on the client - method = getattr(langfuse_client, method_name) - if "current" in method_name: - # Context manager methods - with method(**kwargs) as obj: - if hasattr(obj, "end"): - obj.end() - else: - # Regular methods - obj = method(**kwargs) - if hasattr(obj, "end"): - obj.end() - - elif target == "span": - # Test deprecated methods on spans - span = langfuse_client.start_span(name="test_parent") - method = getattr(span, method_name) - if "current" in method_name: - # Context manager methods - with method(**kwargs) as obj: - if hasattr(obj, "end"): - obj.end() - else: - # Regular methods - obj = method(**kwargs) - if hasattr(obj, "end"): - obj.end() - span.end() - - except Exception: - pass - - # Check that a deprecation warning was emitted - deprecation_warnings = [ - w for w in warning_list if issubclass(w.category, DeprecationWarning) - ] - assert len(deprecation_warnings) > 0, ( - f"No DeprecationWarning emitted for {target}.{method_name}" - ) - - # Check that the warning message matches expected - warning_messages = [str(w.message) for w in deprecation_warnings] - assert expected_message in warning_messages, ( - f"Expected warning message not found for {target}.{method_name}. Got: {warning_messages}" - ) diff --git a/tests/test_error_parsing.py b/tests/test_error_parsing.py index db53f3d4d..36a7decbf 100644 --- a/tests/test_error_parsing.py +++ b/tests/test_error_parsing.py @@ -5,14 +5,14 @@ generate_error_message_fern, ) from langfuse._utils.request import APIError, APIErrors -from langfuse.api.core import ApiError -from langfuse.api.resources.commons.errors import ( +from langfuse.api import ( AccessDeniedError, MethodNotAllowedError, NotFoundError, + ServiceUnavailableError, UnauthorizedError, ) -from langfuse.api.resources.health.errors import ServiceUnavailableError +from langfuse.api.core import ApiError def test_generate_error_message_api_error(): diff --git a/tests/test_json.py b/tests/test_json.py index 26b6919fa..1f7ef6ece 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -12,7 +12,7 @@ import langfuse from langfuse._utils.serializer import EventSerializer -from langfuse.api.resources.commons.types.observation_level import ObservationLevel +from langfuse.api import ObservationLevel class TestModel(BaseModel): diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 14c25446f..575cbf150 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -14,7 +14,7 @@ from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import END, START, MessagesState, StateGraph from langgraph.prebuilt import ToolNode -from pydantic.v1 import BaseModel, Field +from pydantic import BaseModel, Field from langfuse._client.client import Langfuse from langfuse.langchain import CallbackHandler @@ -26,7 +26,7 @@ def test_callback_generated_from_trace_chat(): trace_id = create_uuid() - with langfuse.start_as_current_span(name="parent") as span: + with langfuse.start_as_current_observation(name="parent") as span: trace_id = span.trace_id handler = CallbackHandler() chat = ChatOpenAI(temperature=0) @@ -72,7 +72,7 @@ def test_callback_generated_from_trace_chat(): def test_callback_generated_from_lcel_chain(): langfuse = Langfuse() - with langfuse.start_as_current_span(name="parent") as span: + with langfuse.start_as_current_observation(name="parent") as span: trace_id = span.trace_id handler = CallbackHandler() prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}") @@ -159,7 +159,7 @@ def test_basic_chat_openai(): def test_callback_simple_openai(): langfuse = Langfuse() - with langfuse.start_as_current_span(name="simple_openai_test") as span: + with langfuse.start_as_current_observation(name="simple_openai_test") as span: trace_id = span.trace_id # Create a unique name for this test @@ -176,7 +176,7 @@ def test_callback_simple_openai(): llm.invoke(text, config={"callbacks": [handler], "run_name": test_name}) # Ensure data is flushed to API - handler.client.flush() + handler._langfuse_client.flush() sleep(2) # Retrieve trace @@ -199,7 +199,9 @@ def test_callback_simple_openai(): def test_callback_multiple_invocations_on_different_traces(): langfuse = Langfuse() - with langfuse.start_as_current_span(name="multiple_invocations_test") as span: + with langfuse.start_as_current_observation( + name="multiple_invocations_test" + ) as span: trace_id = span.trace_id # Create unique names for each test @@ -220,7 +222,7 @@ def test_callback_multiple_invocations_on_different_traces(): handler2 = CallbackHandler() llm.invoke(text, config={"callbacks": [handler2], "run_name": test_name_2}) - handler1.client.flush() + handler1._langfuse_client.flush() # Ensure data is flushed to API sleep(2) @@ -245,7 +247,9 @@ def test_callback_multiple_invocations_on_different_traces(): def test_openai_instruct_usage(): langfuse = Langfuse() - with langfuse.start_as_current_span(name="openai_instruct_usage_test") as span: + with langfuse.start_as_current_observation( + name="openai_instruct_usage_test" + ) as span: trace_id = span.trace_id from langchain_core.output_parsers.string import StrOutputParser from langchain_core.runnables import Runnable @@ -277,7 +281,7 @@ def test_openai_instruct_usage(): ] runnable_chain.batch(input_list) - lf_handler.client.flush() + lf_handler._langfuse_client.flush() observations = get_api().trace.get(trace_id).observations @@ -437,7 +441,7 @@ def test_link_langfuse_prompts_invoke(): # Run chain langfuse_handler = CallbackHandler() - with langfuse.start_as_current_span(name=trace_name) as span: + with langfuse.start_as_current_observation(name=trace_name) as span: trace_id = span.trace_id chain.invoke( {"animal": "dog"}, @@ -447,7 +451,7 @@ def test_link_langfuse_prompts_invoke(): }, ) - langfuse_handler.client.flush() + langfuse_handler._langfuse_client.flush() sleep(2) trace = get_api().trace.get(trace_id=trace_id) @@ -520,7 +524,7 @@ def test_link_langfuse_prompts_stream(): # Run chain langfuse_handler = CallbackHandler() - with langfuse.start_as_current_span(name=trace_name) as span: + with langfuse.start_as_current_observation(name=trace_name) as span: trace_id = span.trace_id stream = chain.stream( {"animal": "dog"}, @@ -534,7 +538,7 @@ def test_link_langfuse_prompts_stream(): for chunk in stream: output += chunk - langfuse_handler.client.flush() + langfuse_handler._langfuse_client.flush() sleep(2) trace = get_api().trace.get(trace_id=trace_id) @@ -610,7 +614,7 @@ def test_link_langfuse_prompts_batch(): # Run chain langfuse_handler = CallbackHandler() - with langfuse.start_as_current_span(name=trace_name) as span: + with langfuse.start_as_current_observation(name=trace_name) as span: trace_id = span.trace_id chain.batch( [{"animal": "dog"}, {"animal": "cat"}, {"animal": "elephant"}], @@ -620,7 +624,7 @@ def test_link_langfuse_prompts_batch(): }, ) - langfuse_handler.client.flush() + langfuse_handler._langfuse_client.flush() traces = get_api().trace.list(name=trace_name).data @@ -743,13 +747,13 @@ class GetWeather(BaseModel): } ] - with handler.client.start_as_current_span( + with handler._langfuse_client.start_as_current_observation( name="test_callback_openai_functions_with_tools" ) as span: trace_id = span.trace_id llm.bind_tools([address_tool, weather_tool]).invoke(messages) - handler.client.flush() + handler._langfuse_client.flush() trace = get_api().trace.get(trace_id=trace_id) @@ -806,7 +810,7 @@ def _generate_random_dict(n: int, key_length: int = 8) -> Dict[str, Any]: handler = CallbackHandler() langfuse = Langfuse() - with langfuse.start_as_current_span(name="test_langfuse_overhead"): + with langfuse.start_as_current_observation(name="test_langfuse_overhead"): test_chain.invoke(inputs, config={"callbacks": [handler]}) duration_with_langfuse = (time.monotonic() - start) * 1000 @@ -842,11 +846,13 @@ def test_multimodal(): ], ) - with handler.client.start_as_current_span(name="test_multimodal") as span: + with handler._langfuse_client.start_as_current_observation( + name="test_multimodal" + ) as span: trace_id = span.trace_id model.invoke([message], config={"callbacks": [handler]}) - handler.client.flush() + handler._langfuse_client.flush() trace = get_api().trace.get(trace_id=trace_id) @@ -931,14 +937,16 @@ def call_model(state: MessagesState): handler = CallbackHandler() # Use the Runnable - with handler.client.start_as_current_span(name="test_langgraph") as span: + with handler._langfuse_client.start_as_current_observation( + name="test_langgraph" + ) as span: trace_id = span.trace_id final_state = app.invoke( {"messages": [HumanMessage(content="what is the weather in sf")]}, config={"configurable": {"thread_id": 42}, "callbacks": [handler]}, ) print(final_state["messages"][-1].content) - handler.client.flush() + handler._langfuse_client.flush() trace = get_api().trace.get(trace_id=trace_id) @@ -971,7 +979,7 @@ def test_cached_token_usage(): # invoke again to force cached token usage chain.invoke({"test_param": "in a funny way"}, config) - handler.client.flush() + handler._langfuse_client.flush() trace = get_api().trace.get(handler.get_trace_id()) diff --git a/tests/test_langchain_integration.py b/tests/test_langchain_integration.py index 599860b50..c7e4a9418 100644 --- a/tests/test_langchain_integration.py +++ b/tests/test_langchain_integration.py @@ -33,7 +33,7 @@ def test_stream_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): res = model.stream( @@ -88,7 +88,7 @@ def test_stream_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): res = model.stream( @@ -142,7 +142,7 @@ def test_invoke_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): _ = model.invoke( @@ -192,7 +192,7 @@ def test_invoke_in_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): test_phrase = "This is a test!" @@ -241,7 +241,7 @@ def test_batch_in_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): input1 = "Who is the first president of America ?" @@ -290,7 +290,7 @@ def test_batch_in_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): input1 = "Who is the first president of America ?" @@ -342,7 +342,7 @@ async def test_astream_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): res = model.astream( @@ -398,7 +398,7 @@ async def test_astream_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): test_phrase = "This is a test!" @@ -454,7 +454,7 @@ async def test_ainvoke_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): test_phrase = "This is a test!" @@ -505,7 +505,7 @@ async def test_ainvoke_in_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): test_phrase = "This is a test!" @@ -558,7 +558,7 @@ def test_chains_batch_in_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt = ChatPromptTemplate.from_template( @@ -609,7 +609,7 @@ def test_chains_batch_in_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt = ChatPromptTemplate.from_template( @@ -662,7 +662,7 @@ async def test_chains_abatch_in_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt = ChatPromptTemplate.from_template( @@ -715,7 +715,7 @@ async def test_chains_abatch_in_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt = ChatPromptTemplate.from_template( @@ -764,7 +764,7 @@ async def test_chains_ainvoke_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt1 = ChatPromptTemplate.from_template( @@ -820,7 +820,7 @@ async def test_chains_ainvoke_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt1 = PromptTemplate.from_template( @@ -876,7 +876,7 @@ async def test_chains_astream_chat_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt1 = PromptTemplate.from_template( @@ -938,7 +938,7 @@ async def test_chains_astream_completions_models(model_name): langfuse_client = handler.client trace_id = Langfuse.create_trace_id() - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name=name, trace_context={"trace_id": trace_id} ): prompt1 = PromptTemplate.from_template( diff --git a/tests/test_media.py b/tests/test_media.py index bea232bf5..6c095ece1 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -157,7 +157,7 @@ def test_replace_media_reference_string_in_object(): mock_trace_name = f"test-trace-with-audio-{uuid4()}" base64_audio = base64.b64encode(mock_audio_bytes).decode() - span = langfuse.start_span( + span = langfuse.start_observation( name=mock_trace_name, metadata={ "context": { @@ -188,7 +188,7 @@ def test_replace_media_reference_string_in_object(): assert resolved_obs["metadata"]["context"]["nested"] == expected_base64 # Create second trace reusing the media reference - span2 = langfuse.start_span( + span2 = langfuse.start_observation( name=f"2-{mock_trace_name}", metadata={"context": {"nested": resolved_obs["metadata"]["context"]["nested"]}}, ).end() diff --git a/tests/test_otel.py b/tests/test_otel.py index 89c028c68..c0f42fc73 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -14,6 +14,7 @@ ) from opentelemetry.sdk.trace.id_generator import RandomIdGenerator +from langfuse import propagate_attributes from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse._client.client import Langfuse from langfuse._client.resource_manager import LangfuseResourceManager @@ -82,11 +83,16 @@ def mock_processor_init(self, monkeypatch, memory_exporter): def mock_init(self, **kwargs): from opentelemetry.sdk.trace.export import BatchSpanProcessor + from langfuse._client.span_filter import is_default_export_span + self.public_key = kwargs.get("public_key", "test-key") blocked_scopes = kwargs.get("blocked_instrumentation_scopes") self.blocked_instrumentation_scopes = ( blocked_scopes if blocked_scopes is not None else [] ) + self._should_export_span = ( + kwargs.get("should_export_span") or is_default_export_span + ) BatchSpanProcessor.__init__( self, span_exporter=memory_exporter, @@ -248,7 +254,9 @@ class TestBasicSpans(TestOTelBase): def test_basic_span_creation(self, langfuse_client, memory_exporter): """Test that a basic span can be created with attributes.""" # Create a span and end it - span = langfuse_client.start_span(name="test-span", input={"test": "value"}) + span = langfuse_client.start_observation( + name="test-span", input={"test": "value"} + ) span.end() # Get spans with our name @@ -273,15 +281,19 @@ def test_basic_span_creation(self, langfuse_client, memory_exporter): def test_span_hierarchy(self, langfuse_client, memory_exporter): """Test creating nested spans and verify their parent-child relationships.""" # Create parent span - with langfuse_client.start_as_current_span(name="parent-span") as parent_span: + with langfuse_client.start_as_current_observation( + name="parent-span" + ) as parent_span: # Create a child span - child_span = parent_span.start_span(name="child-span") + child_span = parent_span.start_observation(name="child-span") child_span.end() # Create another child span using context manager - with parent_span.start_as_current_span(name="child-span-2") as child_span_2: + with parent_span.start_as_current_observation( + name="child-span-2" + ) as child_span_2: # Create a grandchild span - grandchild = child_span_2.start_span(name="grandchild-span") + grandchild = child_span_2.start_observation(name="grandchild-span") grandchild.end() # Get all spans @@ -312,7 +324,7 @@ def test_span_hierarchy(self, langfuse_client, memory_exporter): def test_update_current_span_name(self, langfuse_client, memory_exporter): """Test updating current span name via update_current_span method.""" # Create a span using context manager - with langfuse_client.start_as_current_span(name="original-current-span"): + with langfuse_client.start_as_current_observation(name="original-current-span"): # Update the current span name langfuse_client.update_current_span(name="updated-current-span") @@ -329,7 +341,7 @@ def test_update_current_span_name(self, langfuse_client, memory_exporter): def test_span_attributes(self, langfuse_client, memory_exporter): """Test that span attributes are correctly set and updated.""" # Create a span with attributes - span = langfuse_client.start_span( + span = langfuse_client.start_observation( name="attribute-span", input={"prompt": "Test prompt"}, output={"response": "Test response"}, @@ -380,7 +392,7 @@ def test_span_attributes(self, langfuse_client, memory_exporter): def test_span_name_update(self, langfuse_client, memory_exporter): """Test updating span name via update method.""" # Create a span with initial name - span = langfuse_client.start_span(name="original-span-name") + span = langfuse_client.start_observation(name="original-span-name") # Update the span name span.update(name="updated-span-name") @@ -397,7 +409,8 @@ def test_span_name_update(self, langfuse_client, memory_exporter): def test_generation_span(self, langfuse_client, memory_exporter): """Test creating a generation span with model-specific attributes.""" # Create a generation - generation = langfuse_client.start_generation( + generation = langfuse_client.start_observation( + as_type="generation", name="test-generation", model="gpt-4", model_parameters={"temperature": 0.7, "max_tokens": 100}, @@ -431,8 +444,10 @@ def test_generation_span(self, langfuse_client, memory_exporter): def test_generation_name_update(self, langfuse_client, memory_exporter): """Test updating generation name via update method.""" # Create a generation with initial name - generation = langfuse_client.start_generation( - name="original-generation-name", model="gpt-4" + generation = langfuse_client.start_observation( + as_type="generation", + name="original-generation-name", + model="gpt-4", ) # Update the generation name @@ -451,16 +466,16 @@ def test_generation_name_update(self, langfuse_client, memory_exporter): def test_trace_update(self, langfuse_client, memory_exporter): """Test updating trace level attributes.""" - # Create a span and update trace attributes - with langfuse_client.start_as_current_span(name="trace-span") as span: - span.update_trace( - name="updated-trace-name", + # Create a span and set trace attributes using propagate_attributes and set_trace_io + with langfuse_client.start_as_current_observation(name="trace-span") as span: + with propagate_attributes( + trace_name="updated-trace-name", user_id="test-user", session_id="test-session", tags=["tag1", "tag2"], - input={"trace-input": "value"}, metadata={"trace-meta": "data"}, - ) + ): + span.set_trace_io(input={"trace-input": "value"}) # Get the span data spans = self.get_spans_by_name(memory_exporter, "trace-span") @@ -490,34 +505,40 @@ def test_trace_update(self, langfuse_client, memory_exporter): def test_complex_scenario(self, langfuse_client, memory_exporter): """Test a more complex scenario with multiple operations and nesting.""" # Create a trace with a main span - with langfuse_client.start_as_current_span(name="main-flow") as main_span: + with langfuse_client.start_as_current_observation( + name="main-flow" + ) as main_span: # Add trace information - main_span.update_trace( - name="complex-test", + with propagate_attributes( + trace_name="complex-test", user_id="complex-user", session_id="complex-session", - ) - - # Add a processing span - with main_span.start_as_current_span(name="processing") as processing: - processing.update(metadata={"step": "processing"}) - - # Add an LLM generation - with main_span.start_as_current_generation( - name="llm-call", - model="gpt-3.5-turbo", - input={"prompt": "Summarize this text"}, - metadata={"service": "OpenAI"}, - ) as generation: - # Update the generation with results - generation.update( - output={"text": "This is a summary"}, - usage_details={"input": 20, "output": 5, "total": 25}, - ) + ): + # Add a processing span + with main_span.start_as_current_observation( + name="processing" + ) as processing: + processing.update(metadata={"step": "processing"}) + + # Add an LLM generation + with main_span.start_as_current_observation( + as_type="generation", + name="llm-call", + model="gpt-3.5-turbo", + input={"prompt": "Summarize this text"}, + metadata={"service": "OpenAI"}, + ) as generation: + # Update the generation with results + generation.update( + output={"text": "This is a summary"}, + usage_details={"input": 20, "output": 5, "total": 25}, + ) - # Final processing step - with main_span.start_as_current_span(name="post-processing") as post_proc: - post_proc.update(metadata={"step": "post-processing"}) + # Final processing step + with main_span.start_as_current_observation( + name="post-processing" + ) as post_proc: + post_proc.update(metadata={"step": "post-processing"}) # Get all spans spans = [ @@ -572,8 +593,10 @@ def test_complex_scenario(self, langfuse_client, memory_exporter): def test_update_current_generation_name(self, langfuse_client, memory_exporter): """Test updating current generation name via update_current_generation method.""" # Create a generation using context manager - with langfuse_client.start_as_current_generation( - name="original-current-generation", model="gpt-4" + with langfuse_client.start_as_current_observation( + as_type="generation", + name="original-current-generation", + model="gpt-4", ): # Update the current generation name langfuse_client.update_current_generation(name="updated-current-generation") @@ -606,8 +629,9 @@ def test_start_as_current_observation_types(self, langfuse_client, memory_export for obs_type in observation_types: with langfuse_client.start_as_current_observation( name=f"test-{obs_type}", as_type=obs_type - ) as obs: - obs.update_trace(name=f"trace-{obs_type}") + ): + with propagate_attributes(trace_name=f"trace-{obs_type}"): + pass spans = [ self.get_span_data(span) for span in memory_exporter.get_finished_spans() @@ -643,7 +667,7 @@ def test_start_observation(self, langfuse_client, memory_exporter): observation_types = get_observation_types_list(ObservationTypeLiteral) # Create a main span to use for child creation - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name="factory-test-parent" ) as parent_span: created_observations = [] @@ -773,7 +797,7 @@ def test_custom_trace_id(self, langfuse_client, memory_exporter): # Create a span with this custom trace ID using trace_context trace_context = {"trace_id": custom_trace_id} - span = langfuse_client.start_span( + span = langfuse_client.start_observation( name="custom-trace-span", trace_context=trace_context, input={"test": "value"}, @@ -791,7 +815,7 @@ def test_custom_trace_id(self, langfuse_client, memory_exporter): assert span_data["attributes"][LangfuseOtelSpanAttributes.AS_ROOT] is True # Test additional spans with the same trace context - child_span = langfuse_client.start_span( + child_span = langfuse_client.start_observation( name="child-span", trace_context=trace_context, input={"child": "data"} ) child_span.end() @@ -813,7 +837,7 @@ def test_custom_parent_span_id(self, langfuse_client, memory_exporter): trace_context = {"trace_id": trace_id, "parent_span_id": parent_span_id} # Create a span with this context - span = langfuse_client.start_span( + span = langfuse_client.start_observation( name="custom-parent-span", trace_context=trace_context ) span.end() @@ -827,9 +851,12 @@ def test_custom_parent_span_id(self, langfuse_client, memory_exporter): def test_multiple_generations_in_trace(self, langfuse_client, memory_exporter): """Test creating multiple generation spans within the same trace.""" # Create a trace with multiple generation spans - with langfuse_client.start_as_current_span(name="multi-gen-flow") as main_span: + with langfuse_client.start_as_current_observation( + name="multi-gen-flow" + ) as main_span: # First generation - gen1 = main_span.start_generation( + gen1 = main_span.start_observation( + as_type="generation", name="generation-1", model="gpt-3.5-turbo", input={"prompt": "First prompt"}, @@ -840,7 +867,8 @@ def test_multiple_generations_in_trace(self, langfuse_client, memory_exporter): gen1.end() # Second generation with different model - gen2 = main_span.start_generation( + gen2 = main_span.start_observation( + as_type="generation", name="generation-2", model="gpt-4", input={"prompt": "Second prompt"}, @@ -909,7 +937,7 @@ def test_multiple_generations_in_trace(self, langfuse_client, memory_exporter): def test_error_handling(self, langfuse_client, memory_exporter): """Test error handling in span operations.""" # Create a span that will have an error - span = langfuse_client.start_span(name="error-span") + span = langfuse_client.start_observation(name="error-span") # Set an error status on the span import traceback @@ -947,7 +975,7 @@ def test_error_handling(self, langfuse_client, memory_exporter): def test_error_level_in_span_creation(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when creating spans with level='ERROR'.""" # Create a span with level="ERROR" at creation time - span = langfuse_client.start_span( + span = langfuse_client.start_observation( name="create-error-span", level="ERROR", status_message="Initial error state", @@ -982,7 +1010,7 @@ def test_error_level_in_span_creation(self, langfuse_client, memory_exporter): def test_error_level_in_span_update(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when updating spans to level='ERROR'.""" # Create a normal span - span = langfuse_client.start_span(name="update-error-span", level="INFO") + span = langfuse_client.start_observation(name="update-error-span", level="INFO") # Update it to ERROR level span.update(level="ERROR", status_message="Updated to error state") @@ -1016,7 +1044,8 @@ def test_error_level_in_span_update(self, langfuse_client, memory_exporter): def test_generation_error_level_in_creation(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when creating generations with level='ERROR'.""" # Create a generation with level="ERROR" at creation time - generation = langfuse_client.start_generation( + generation = langfuse_client.start_observation( + as_type="generation", name="create-error-generation", model="gpt-4", level="ERROR", @@ -1052,8 +1081,11 @@ def test_generation_error_level_in_creation(self, langfuse_client, memory_export def test_generation_error_level_in_update(self, langfuse_client, memory_exporter): """Test that OTEL span status is set to ERROR when updating generations to level='ERROR'.""" # Create a normal generation - generation = langfuse_client.start_generation( - name="update-error-generation", model="gpt-4", level="INFO" + generation = langfuse_client.start_observation( + as_type="generation", + name="update-error-generation", + model="gpt-4", + level="INFO", ) # Update it to ERROR level @@ -1096,7 +1128,7 @@ def test_non_error_levels_dont_set_otel_status( for i, level in enumerate(test_levels): span_name = f"non-error-span-{i}" - span = langfuse_client.start_span(name=span_name, level=level) + span = langfuse_client.start_observation(name=span_name, level=level) # Update with same level to test update path too if level is not None: @@ -1122,7 +1154,7 @@ def test_non_error_levels_dont_set_otel_status( def test_multiple_error_updates(self, langfuse_client, memory_exporter): """Test that multiple ERROR level updates work correctly.""" # Create a span - span = langfuse_client.start_span(name="multi-error-span") + span = langfuse_client.start_observation(name="multi-error-span") # First error update span.update(level="ERROR", status_message="First error") @@ -1150,7 +1182,9 @@ def test_multiple_error_updates(self, langfuse_client, memory_exporter): def test_error_without_status_message(self, langfuse_client, memory_exporter): """Test that ERROR level works even without status_message.""" # Create a span with ERROR level but no status message - span = langfuse_client.start_span(name="error-no-message-span", level="ERROR") + span = langfuse_client.start_observation( + name="error-no-message-span", level="ERROR" + ) span.end() # Get the raw OTEL spans to check the status @@ -1185,7 +1219,9 @@ def test_different_observation_types_error_handling( ] # Create a parent span for child observations - with langfuse_client.start_as_current_span(name="error-test-parent") as parent: + with langfuse_client.start_as_current_observation( + name="error-test-parent" + ) as parent: for obs_type in observation_types: # Create observation with ERROR level obs = parent.start_observation( @@ -1252,7 +1288,8 @@ def test_complex_model_parameters(self, langfuse_client, memory_exporter): } # Create a generation with these complex parameters - generation = langfuse_client.start_generation( + generation = langfuse_client.start_observation( + as_type="generation", name="complex-params-test", model="gpt-4", model_parameters=complex_params, @@ -1291,7 +1328,8 @@ def test_complex_model_parameters(self, langfuse_client, memory_exporter): def test_updating_current_generation(self, langfuse_client, memory_exporter): """Test that an in-progress generation can be updated multiple times.""" # Create a generation - generation = langfuse_client.start_generation( + generation = langfuse_client.start_observation( + as_type="generation", name="updating-generation", model="gpt-4", input={"prompt": "Write a story about a robot"}, @@ -1383,7 +1421,7 @@ def test_sampling(self, monkeypatch, tracer_provider, mock_processor_init): # Create several spans for i in range(5): - span = client.start_span(name=f"sampled-span-{i}") + span = client.start_observation(name=f"sampled-span-{i}") span.end() # With a sample rate of 0, we should have no spans @@ -1398,7 +1436,7 @@ def test_sampling(self, monkeypatch, tracer_provider, mock_processor_init): def test_shutdown_and_flush(self, langfuse_client, memory_exporter): """Test shutdown and flush operations.""" # Create a span without ending it - span = langfuse_client.start_span(name="flush-test-span") + span = langfuse_client.start_observation(name="flush-test-span") # Explicitly flush langfuse_client.flush() @@ -1415,7 +1453,7 @@ def test_shutdown_and_flush(self, langfuse_client, memory_exporter): assert len(spans) == 1, "Span should be exported after ending" # Create another span for shutdown testing - langfuse_client.start_span(name="shutdown-test-span") + langfuse_client.start_observation(name="shutdown-test-span") # Call shutdown (should flush any pending spans) langfuse_client.shutdown() @@ -1436,12 +1474,13 @@ def test_disabled_tracing(self, monkeypatch, tracer_provider, mock_processor_ini tracer_provider.add_span_processor(processor) # Attempt to create spans and trace operations - span = client.start_span(name="disabled-span", input={"key": "value"}) + span = client.start_observation(name="disabled-span", input={"key": "value"}) span.update(output={"result": "test"}) span.end() - with client.start_as_current_span(name="disabled-context-span") as context_span: - context_span.update_trace(name="disabled-trace") + with client.start_as_current_observation(name="disabled-context-span"): + with propagate_attributes(trace_name="disabled-trace"): + pass # Verify no spans were created spans = exporter.get_finished_spans() @@ -1946,7 +1985,12 @@ def multi_project_setup(self, monkeypatch): def mock_processor_init(self, **kwargs): from opentelemetry.sdk.trace.export import BatchSpanProcessor + from langfuse._client.span_filter import is_default_export_span + self.public_key = kwargs.get("public_key", "test-key") + self._should_export_span = ( + kwargs.get("should_export_span") or is_default_export_span + ) # Use the appropriate exporter based on the project key if self.public_key == project1_key: exporter = exporter_project1 @@ -2031,12 +2075,12 @@ def mock_initialize(self, **kwargs): def test_spans_routed_to_correct_exporters(self, multi_project_setup): """Test that spans are routed to the correct exporters based on public key.""" # Create spans in both projects - span1 = multi_project_setup["langfuse_project1"].start_span( + span1 = multi_project_setup["langfuse_project1"].start_observation( name="trace-project1", metadata={"project": "project1"} ) span1.end() - span2 = multi_project_setup["langfuse_project2"].start_span( + span2 = multi_project_setup["langfuse_project2"].start_observation( name="trace-project2", metadata={"project": "project2"} ) span2.end() @@ -2069,7 +2113,7 @@ def test_concurrent_operations_in_multiple_projects(self, multi_project_setup): # Create simple non-nested spans in separate threads def create_spans_project1(): for i in range(5): - span = multi_project_setup["langfuse_project1"].start_span( + span = multi_project_setup["langfuse_project1"].start_observation( name=f"project1-span-{i}", metadata={"project": "project1", "index": i}, ) @@ -2079,7 +2123,7 @@ def create_spans_project1(): def create_spans_project2(): for i in range(5): - span = multi_project_setup["langfuse_project2"].start_span( + span = multi_project_setup["langfuse_project2"].start_observation( name=f"project2-span-{i}", metadata={"project": "project2", "index": i}, ) @@ -2123,12 +2167,12 @@ def create_spans_project2(): def test_span_processor_filtering(self, multi_project_setup): """Test that spans are correctly filtered to the right exporters.""" # Create spans with identical attributes in both projects - span1 = multi_project_setup["langfuse_project1"].start_span( + span1 = multi_project_setup["langfuse_project1"].start_observation( name="test-filter-span", metadata={"project": "shared-value"} ) span1.end() - span2 = multi_project_setup["langfuse_project2"].start_span( + span2 = multi_project_setup["langfuse_project2"].start_observation( name="test-filter-span", metadata={"project": "shared-value"} ) span2.end() @@ -2168,12 +2212,12 @@ def test_context_isolation_between_projects(self, multi_project_setup): # Simplified version that just tests separate span routing # Start spans in both projects with the same name - span1 = multi_project_setup["langfuse_project1"].start_span( + span1 = multi_project_setup["langfuse_project1"].start_observation( name="identical-span-name" ) span1.end() - span2 = multi_project_setup["langfuse_project2"].start_span( + span2 = multi_project_setup["langfuse_project2"].start_observation( name="identical-span-name" ) span2.end() @@ -2199,13 +2243,13 @@ def test_cross_project_tracing(self, multi_project_setup): # Create a cross-project sequence that should not share context # Start a span in project1 - span1 = multi_project_setup["langfuse_project1"].start_span( + span1 = multi_project_setup["langfuse_project1"].start_observation( name="cross-project-parent" ) # Without ending span1, create a span in project2 # This should NOT inherit context from span1 even though it's active - span2 = multi_project_setup["langfuse_project2"].start_span( + span2 = multi_project_setup["langfuse_project2"].start_observation( name="independent-project2-span" ) @@ -2247,12 +2291,12 @@ def test_sdk_client_isolation(self, multi_project_setup): # Each client should have different trace IDs # Create two spans with identical attributes in both projects - span1 = multi_project_setup["langfuse_project1"].start_span( + span1 = multi_project_setup["langfuse_project1"].start_observation( name="isolation-test-span" ) span1.end() - span2 = multi_project_setup["langfuse_project2"].start_span( + span2 = multi_project_setup["langfuse_project2"].start_observation( name="isolation-test-span" ) span2.end() @@ -2316,11 +2360,16 @@ def instrumentation_filtering_setup(self, monkeypatch): def mock_processor_init(self, **kwargs): from opentelemetry.sdk.trace.export import BatchSpanProcessor + from langfuse._client.span_filter import is_default_export_span + self.public_key = kwargs.get("public_key", "test-key") blocked_scopes = kwargs.get("blocked_instrumentation_scopes") self.blocked_instrumentation_scopes = ( blocked_scopes if blocked_scopes is not None else [] ) + self._should_export_span = ( + kwargs.get("should_export_span") or is_default_export_span + ) # For testing, use the appropriate exporter based on setup exporter = kwargs.get("_test_exporter", blocked_exporter) @@ -2360,6 +2409,7 @@ def mock_initialize(self, **kwargs): blocked_instrumentation_scopes=kwargs.get( "blocked_instrumentation_scopes" ), + should_export_span=kwargs.get("should_export_span"), ) # Replace its exporter with our test exporter processor._span_exporter = blocked_exporter @@ -2393,193 +2443,299 @@ def mock_initialize(self, **kwargs): ) blocked_exporter.shutdown() - def test_blocked_instrumentation_scopes_export_filtering( + def test_default_filter_exports_langfuse_spans( self, instrumentation_filtering_setup ): - """Test that spans from blocked instrumentation scopes are not exported.""" - # Create Langfuse client with blocked scopes + """Test that the default filter exports Langfuse SDK spans.""" Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", base_url="http://localhost:3000", - blocked_instrumentation_scopes=["openai", "anthropic"], ) - # Get the tracer provider and create different instrumentation scope tracers tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] - - # Create langfuse tracer with proper attributes for project validation langfuse_tracer = tracer_provider.get_tracer( "langfuse-sdk", attributes={"public_key": instrumentation_filtering_setup["test_key"]}, ) - openai_tracer = tracer_provider.get_tracer("openai") - anthropic_tracer = tracer_provider.get_tracer("anthropic") - allowed_tracer = tracer_provider.get_tracer("allowed-library") - # Create spans from each tracer - langfuse_span = langfuse_tracer.start_span("langfuse-span") - langfuse_span.end() + span = langfuse_tracer.start_span("langfuse-span") + span.end() + tracer_provider.force_flush() - openai_span = openai_tracer.start_span("openai-span") - openai_span.end() + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "langfuse-span" in exported_span_names - anthropic_span = anthropic_tracer.start_span("anthropic-span") - anthropic_span.end() + def test_default_filter_exports_genai_spans(self, instrumentation_filtering_setup): + """Test that the default filter exports spans with gen_ai.* attributes.""" + Langfuse( + public_key=instrumentation_filtering_setup["test_key"], + secret_key="test-secret-key", + base_url="http://localhost:3000", + ) - allowed_span = allowed_tracer.start_span("allowed-span") - allowed_span.end() + tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + unknown_tracer = tracer_provider.get_tracer("custom-framework") - # Force flush to ensure all spans are processed + span = unknown_tracer.start_span("genai-span") + span.set_attribute("gen_ai.request.model", "gpt-4o") + span.end() tracer_provider.force_flush() - # Check which spans were actually exported - exported_spans = instrumentation_filtering_setup[ - "blocked_exporter" - ].get_finished_spans() - exported_span_names = [span.name for span in exported_spans] - exported_scope_names = [ - span.instrumentation_scope.name - for span in exported_spans - if span.instrumentation_scope + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() ] + assert "genai-span" in exported_span_names - # Langfuse spans should be exported (not blocked) - assert "langfuse-span" in exported_span_names - assert "langfuse-sdk" in exported_scope_names + def test_default_filter_exports_known_instrumentor_spans( + self, instrumentation_filtering_setup + ): + """Test that the default filter exports spans from known instrumentors.""" + Langfuse( + public_key=instrumentation_filtering_setup["test_key"], + secret_key="test-secret-key", + base_url="http://localhost:3000", + ) - # Blocked scopes should NOT be exported - assert "openai-span" not in exported_span_names - assert "anthropic-span" not in exported_span_names - assert "openai" not in exported_scope_names - assert "anthropic" not in exported_scope_names + tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + known_tracer = tracer_provider.get_tracer("openinference.instrumentation.agno") - # Allowed scopes should be exported - assert "allowed-span" in exported_span_names - assert "allowed-library" in exported_scope_names + span = known_tracer.start_span("known-instrumentor-span") + span.end() + tracer_provider.force_flush() + + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "known-instrumentor-span" in exported_span_names - def test_no_blocked_scopes_allows_all_exports( + def test_default_filter_rejects_unknown_spans( self, instrumentation_filtering_setup ): - """Test that when no scopes are blocked, all spans are exported.""" - # Create Langfuse client with NO blocked scopes + """Test that the default filter drops unknown scopes without gen_ai.* attrs.""" Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", base_url="http://localhost:3000", - blocked_instrumentation_scopes=[], ) - # Get the tracer provider and create different instrumentation scope tracers tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + unknown_tracer = tracer_provider.get_tracer("unknown.scope") - langfuse_tracer = tracer_provider.get_tracer( - "langfuse-sdk", - attributes={"public_key": instrumentation_filtering_setup["test_key"]}, - ) - openai_tracer = tracer_provider.get_tracer("openai") - anthropic_tracer = tracer_provider.get_tracer("anthropic") + span = unknown_tracer.start_span("unknown-span") + span.end() + tracer_provider.force_flush() - # Create spans from each tracer - langfuse_span = langfuse_tracer.start_span("langfuse-span") - langfuse_span.end() + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "unknown-span" not in exported_span_names - openai_span = openai_tracer.start_span("openai-span") - openai_span.end() + def test_custom_should_export_span(self, instrumentation_filtering_setup): + """Test that a custom should_export_span callback controls export.""" + Langfuse( + public_key=instrumentation_filtering_setup["test_key"], + secret_key="test-secret-key", + base_url="http://localhost:3000", + should_export_span=lambda span: span.name.startswith("keep-"), + ) - anthropic_span = anthropic_tracer.start_span("anthropic-span") - anthropic_span.end() + tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + tracer = tracer_provider.get_tracer("unknown.scope") - # Force flush + keep_span = tracer.start_span("keep-span") + keep_span.end() + drop_span = tracer.start_span("drop-span") + drop_span.end() tracer_provider.force_flush() - # Check that ALL spans were exported - exported_spans = instrumentation_filtering_setup[ - "blocked_exporter" - ].get_finished_spans() - exported_span_names = [span.name for span in exported_spans] - - assert "langfuse-span" in exported_span_names - assert "openai-span" in exported_span_names - assert "anthropic-span" in exported_span_names + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "keep-span" in exported_span_names + assert "drop-span" not in exported_span_names - def test_none_blocked_scopes_allows_all_exports( + def test_custom_should_export_span_with_composition( self, instrumentation_filtering_setup ): - """Test that when blocked_scopes is None (default), all spans are exported.""" - # Create Langfuse client with None blocked scopes (default behavior) + """Test composing the default filter with custom scope logic.""" + from langfuse.span_filter import is_default_export_span + Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", base_url="http://localhost:3000", - blocked_instrumentation_scopes=None, + should_export_span=lambda span: is_default_export_span(span) + or ( + span.instrumentation_scope is not None + and span.instrumentation_scope.name.startswith("my-framework") + ), ) - # Get the tracer provider and create different instrumentation scope tracers tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + custom_tracer = tracer_provider.get_tracer("my-framework.worker") + known_tracer = tracer_provider.get_tracer("ai") + unknown_tracer = tracer_provider.get_tracer("unknown.scope") + + custom_span = custom_tracer.start_span("custom-span") + custom_span.end() + known_span = known_tracer.start_span("known-span") + known_span.end() + unknown_span = unknown_tracer.start_span("unknown-span") + unknown_span.end() + tracer_provider.force_flush() - langfuse_tracer = tracer_provider.get_tracer( - "langfuse-sdk", - attributes={"public_key": instrumentation_filtering_setup["test_key"]}, - ) - openai_tracer = tracer_provider.get_tracer("openai") + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "custom-span" in exported_span_names + assert "known-span" in exported_span_names + assert "unknown-span" not in exported_span_names - # Create spans from each tracer - langfuse_span = langfuse_tracer.start_span("langfuse-span") - langfuse_span.end() + def test_blocked_scopes_override_should_export( + self, instrumentation_filtering_setup + ): + """Test that blocked scopes are dropped even when callback allows all.""" + with pytest.warns(DeprecationWarning, match="blocked_instrumentation_scopes"): + Langfuse( + public_key=instrumentation_filtering_setup["test_key"], + secret_key="test-secret-key", + base_url="http://localhost:3000", + blocked_instrumentation_scopes=["my-framework.worker"], + should_export_span=lambda span: True, + ) - openai_span = openai_tracer.start_span("openai-span") - openai_span.end() + tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + blocked_tracer = tracer_provider.get_tracer("my-framework.worker") + allowed_tracer = tracer_provider.get_tracer("custom.allowed") - # Force flush + blocked_span = blocked_tracer.start_span("blocked-span") + blocked_span.end() + allowed_span = allowed_tracer.start_span("allowed-span") + allowed_span.end() tracer_provider.force_flush() - # Check that ALL spans were exported - exported_spans = instrumentation_filtering_setup[ - "blocked_exporter" - ].get_finished_spans() - exported_span_names = [span.name for span in exported_spans] + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "blocked-span" not in exported_span_names + assert "allowed-span" in exported_span_names - assert "langfuse-span" in exported_span_names - assert "openai-span" in exported_span_names + def test_should_export_span_with_none_uses_default( + self, instrumentation_filtering_setup + ): + """Test that None should_export_span falls back to the default filter.""" + Langfuse( + public_key=instrumentation_filtering_setup["test_key"], + secret_key="test-secret-key", + base_url="http://localhost:3000", + should_export_span=None, + ) + + tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + known_tracer = tracer_provider.get_tracer("ai") + unknown_tracer = tracer_provider.get_tracer("unknown.scope") + + known_span = known_tracer.start_span("known-span") + known_span.end() + unknown_span = unknown_tracer.start_span("unknown-span") + unknown_span.end() + tracer_provider.force_flush() + + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "known-span" in exported_span_names + assert "unknown-span" not in exported_span_names + + def test_should_export_span_exception_drops_span_and_logs_error( + self, instrumentation_filtering_setup, caplog + ): + """Test that callback failures log an error and skip exporting that span.""" + caplog.set_level("ERROR", logger="langfuse") + + def _failing_filter(_span): + raise RuntimeError("boom") - def test_blocking_langfuse_sdk_scope_export(self, instrumentation_filtering_setup): - """Test that even Langfuse's own spans are blocked if explicitly specified.""" - # Create Langfuse client that blocks its own instrumentation scope Langfuse( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", base_url="http://localhost:3000", - blocked_instrumentation_scopes=["langfuse-sdk"], + should_export_span=_failing_filter, ) - # Get the tracer provider and create tracers tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + tracer = tracer_provider.get_tracer("unknown.scope") - langfuse_tracer = tracer_provider.get_tracer( - "langfuse-sdk", - attributes={"public_key": instrumentation_filtering_setup["test_key"]}, + span = tracer.start_span("callback-error-span") + span.end() + tracer_provider.force_flush() + + exported_span_names = [ + span.name + for span in instrumentation_filtering_setup[ + "blocked_exporter" + ].get_finished_spans() + ] + assert "callback-error-span" not in exported_span_names + assert any( + "should_export_span callback raised an error" in record.message + for record in caplog.records ) - other_tracer = tracer_provider.get_tracer("other-library") - # Create spans - langfuse_span = langfuse_tracer.start_span("langfuse-span") - langfuse_span.end() + def test_blocked_scope_drop_logs_scope_name( + self, instrumentation_filtering_setup, caplog + ): + """Test that blocked scope drops include scope names in debug logs.""" + caplog.set_level("DEBUG", logger="langfuse") - other_span = other_tracer.start_span("other-span") - other_span.end() + with pytest.warns(DeprecationWarning, match="blocked_instrumentation_scopes"): + Langfuse( + public_key=instrumentation_filtering_setup["test_key"], + secret_key="test-secret-key", + base_url="http://localhost:3000", + blocked_instrumentation_scopes=["my.blocked.scope"], + should_export_span=lambda span: True, + ) - # Force flush - tracer_provider.force_flush() + tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] + blocked_tracer = tracer_provider.get_tracer("my.blocked.scope") - # Check exports - Langfuse spans should be blocked, others allowed - exported_spans = instrumentation_filtering_setup[ - "blocked_exporter" - ].get_finished_spans() - exported_span_names = [span.name for span in exported_spans] + span = blocked_tracer.start_span("blocked-debug-span") + span.end() + tracer_provider.force_flush() - assert "langfuse-span" not in exported_span_names - assert "other-span" in exported_span_names + assert any( + "Dropping span due to blocked instrumentation scope" in record.message + and "my.blocked.scope" in record.message + for record in caplog.records + ) class TestConcurrencyAndAsync(TestOTelBase): @@ -2591,12 +2747,12 @@ async def test_async_span_operations(self, langfuse_client, memory_exporter): import asyncio # Start a main span - main_span = langfuse_client.start_span(name="async-main-span") + main_span = langfuse_client.start_observation(name="async-main-span") # Define an async function that creates and updates spans async def async_task(parent_span, task_id): # Start a child span - child_span = parent_span.start_span(name=f"async-task-{task_id}") + child_span = parent_span.start_observation(name=f"async-task-{task_id}") # Simulate async work await asyncio.sleep(0.1) @@ -2664,7 +2820,7 @@ def test_context_propagation_async(self, langfuse_client, memory_exporter): # Create a main span in thread 1 trace_context = {"trace_id": trace_id} - main_span = langfuse_client.start_span( + main_span = langfuse_client.start_observation( name="main-async-span", trace_context=trace_context ) @@ -2685,7 +2841,7 @@ def thread2_function(): nonlocal thread2_span_id, thread2_trace_id # Access the same trace via trace_id in a different thread - thread2_span = langfuse_client.start_span( + thread2_span = langfuse_client.start_observation( name="thread2-span", trace_context={"trace_id": trace_id} ) @@ -2704,7 +2860,7 @@ def thread3_function(): nonlocal thread3_span_id, thread3_trace_id # Create a child of the main span by providing parent_span_id - thread3_span = langfuse_client.start_span( + thread3_span = langfuse_client.start_observation( name="thread3-span", trace_context={"trace_id": trace_id, "parent_span_id": main_span_id}, ) @@ -2767,13 +2923,13 @@ async def test_span_metadata_updates_in_async_context( ): """Test that span metadata updates preserve nested values in async contexts.""" # Skip if the client setup is causing recursion issues - if not hasattr(langfuse_client, "start_span"): + if not hasattr(langfuse_client, "start_observation"): pytest.skip("Client setup has issues, skipping test") import asyncio # Create a trace with a main span - with langfuse_client.start_as_current_span( + with langfuse_client.start_as_current_observation( name="async-metadata-test" ) as main_span: # Initial metadata with nested structure @@ -2886,7 +3042,7 @@ def test_metrics_and_timing(self, langfuse_client, memory_exporter): start_time = time.time() # Create a span - span = langfuse_client.start_span(name="timing-test-span") + span = langfuse_client.start_observation(name="timing-test-span") # Add a small delay time.sleep(0.1) @@ -3303,7 +3459,7 @@ def test_langfuse_event_update_immutability(self, langfuse_client, caplog): """Test that LangfuseEvent.update() logs a warning and does nothing.""" import logging - parent_span = langfuse_client.start_span(name="parent-span") + parent_span = langfuse_client.start_observation(name="parent-span") event = parent_span.start_observation( name="test-event", diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 6ba5ab85a..6aabb1a96 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -10,8 +10,7 @@ PromptCache, PromptCacheItem, ) -from langfuse.api.resources.commons.errors.not_found_error import NotFoundError -from langfuse.api.resources.prompts import Prompt_Chat, Prompt_Text +from langfuse.api import NotFoundError, Prompt_Chat, Prompt_Text from langfuse.model import ChatPromptClient, TextPromptClient from tests.utils import create_uuid, get_api @@ -312,7 +311,7 @@ def test_compile_with_placeholders( variables, placeholders, expected_len, expected_contents ) -> None: """Test compile_with_placeholders with different variable/placeholder combinations.""" - from langfuse.api.resources.prompts import Prompt_Chat + from langfuse.api import Prompt_Chat from langfuse.model import ChatPromptClient mock_prompt = Prompt_Chat( @@ -656,8 +655,11 @@ def test_prompt_end_to_end(): assert prompt_str == "Hello, world! I hope you are great." assert prompt.config == {"temperature": 0.5} - generation = langfuse.start_generation( - name="mygen", input=prompt_str, prompt=prompt + generation = langfuse.start_observation( + as_type="generation", + name="mygen", + input=prompt_str, + prompt=prompt, ).end() # to check that these do not error @@ -1342,8 +1344,11 @@ def test_do_not_link_observation_if_fallback(): prompt = langfuse.get_prompt("nonexistent_prompt", fallback=fallback_text_prompt) - generation = langfuse.start_generation( - name="mygen", prompt=prompt, input="this is a test input" + generation = langfuse.start_observation( + as_type="generation", + name="mygen", + prompt=prompt, + input="this is a test input", ).end() langfuse.flush() diff --git a/tests/test_prompt_compilation.py b/tests/test_prompt_compilation.py index 039556c5d..1b96a14dd 100644 --- a/tests/test_prompt_compilation.py +++ b/tests/test_prompt_compilation.py @@ -1,7 +1,7 @@ import pytest from langchain_core.prompts import ChatPromptTemplate, PromptTemplate -from langfuse.api.resources.prompts import ChatMessage, Prompt_Chat +from langfuse.api import ChatMessage, Prompt_Chat from langfuse.model import ( ChatPromptClient, Prompt_Text, @@ -735,7 +735,7 @@ def test_chat_prompt_with_json_variables(self): def test_chat_prompt_with_placeholders_langchain(self): """Test that chat prompts with placeholders work correctly with Langchain.""" - from langfuse.api.resources.prompts import Prompt_Chat + from langfuse.api import Prompt_Chat chat_messages = [ ChatMessage( @@ -804,7 +804,7 @@ def test_chat_prompt_with_placeholders_langchain(self): def test_get_langchain_prompt_with_unresolved_placeholders(self): """Test that unresolved placeholders become MessagesPlaceholder objects.""" - from langfuse.api.resources.prompts import Prompt_Chat + from langfuse.api import Prompt_Chat from langfuse.model import ChatPromptClient chat_messages = [ @@ -854,7 +854,7 @@ def test_get_langchain_prompt_with_unresolved_placeholders(self): def test_tool_calls_preservation_in_message_placeholder(): """Test that tool calls are preserved when compiling message placeholders.""" - from langfuse.api.resources.prompts import Prompt_Chat + from langfuse.api import Prompt_Chat chat_messages = [ {"role": "system", "content": "You are a helpful assistant."}, diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py index 83c88e48a..566ba6392 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/test_propagate_attributes.py @@ -7,6 +7,7 @@ import concurrent.futures import time +from datetime import datetime import pytest from opentelemetry.instrumentation.threading import ThreadingInstrumentor @@ -14,6 +15,8 @@ from langfuse import propagate_attributes from langfuse._client.attributes import LangfuseOtelSpanAttributes, _serialize from langfuse._client.constants import LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT +from langfuse._client.datasets import DatasetClient +from langfuse.api import Dataset, DatasetItem, DatasetStatus from tests.test_otel import TestOTelBase @@ -78,12 +81,12 @@ class TestPropagateAttributesBasic(TestPropagateAttributesBase): def test_user_id_propagates_to_child_spans(self, langfuse_client, memory_exporter): """Verify user_id propagates to all child spans within context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="test_user_123"): - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() # Verify both children have user_id @@ -105,12 +108,12 @@ def test_session_id_propagates_to_child_spans( self, langfuse_client, memory_exporter ): """Verify session_id propagates to all child spans within context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(session_id="session_abc"): - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() # Verify both children have session_id @@ -130,14 +133,14 @@ def test_session_id_propagates_to_child_spans( def test_metadata_propagates_to_child_spans(self, langfuse_client, memory_exporter): """Verify metadata propagates to all child spans within context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( metadata={"experiment": "variant_a", "version": "1.0"} ): - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() # Verify both children have metadata @@ -167,13 +170,13 @@ def test_metadata_propagates_to_child_spans(self, langfuse_client, memory_export def test_all_attributes_propagate_together(self, langfuse_client, memory_exporter): """Verify user_id, session_id, and metadata all propagate together.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( user_id="user_123", session_id="session_abc", metadata={"experiment": "test", "env": "prod"}, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has all attributes @@ -201,15 +204,15 @@ class TestPropagateAttributesHierarchy(TestPropagateAttributesBase): def test_propagation_to_direct_children(self, langfuse_client, memory_exporter): """Verify attributes propagate to all direct children.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user_123"): - child1 = langfuse_client.start_span(name="child-1") + child1 = langfuse_client.start_observation(name="child-1") child1.end() - child2 = langfuse_client.start_span(name="child-2") + child2 = langfuse_client.start_observation(name="child-2") child2.end() - child3 = langfuse_client.start_span(name="child-3") + child3 = langfuse_client.start_observation(name="child-3") child3.end() # Verify all three children have user_id @@ -221,10 +224,12 @@ def test_propagation_to_direct_children(self, langfuse_client, memory_exporter): def test_propagation_to_grandchildren(self, langfuse_client, memory_exporter): """Verify attributes propagate through multiple levels of nesting.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user_123", session_id="session_abc"): - with langfuse_client.start_as_current_span(name="child-span"): - grandchild = langfuse_client.start_span(name="grandchild-span") + with langfuse_client.start_as_current_observation(name="child-span"): + grandchild = langfuse_client.start_observation( + name="grandchild-span" + ) grandchild.end() # Verify all three levels have attributes @@ -244,10 +249,10 @@ def test_propagation_across_observation_types( self, langfuse_client, memory_exporter ): """Verify attributes propagate to different observation types.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user_123"): # Create span - span = langfuse_client.start_span(name="test-span") + span = langfuse_client.start_observation(name="test-span") span.end() # Create generation @@ -275,16 +280,16 @@ def test_early_propagation_all_spans_covered( self, langfuse_client, memory_exporter ): """Verify setting attributes early covers all child spans.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): # Set attributes BEFORE creating any children with propagate_attributes(user_id="user_123"): - child1 = langfuse_client.start_span(name="child-1") + child1 = langfuse_client.start_observation(name="child-1") child1.end() - child2 = langfuse_client.start_span(name="child-2") + child2 = langfuse_client.start_observation(name="child-2") child2.end() - child3 = langfuse_client.start_span(name="child-3") + child3 = langfuse_client.start_observation(name="child-3") child3.end() # Verify ALL children have user_id @@ -298,15 +303,15 @@ def test_late_propagation_only_future_spans_covered( self, langfuse_client, memory_exporter ): """Verify late propagation only affects spans created after context entry.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): # Create child1 BEFORE propagate_attributes - child1 = langfuse_client.start_span(name="child-1") + child1 = langfuse_client.start_observation(name="child-1") child1.end() # NOW set attributes with propagate_attributes(user_id="user_123"): # Create child2 AFTER propagate_attributes - child2 = langfuse_client.start_span(name="child-2") + child2 = langfuse_client.start_observation(name="child-2") child2.end() # Verify: child1 does NOT have user_id, child2 DOES @@ -322,7 +327,7 @@ def test_late_propagation_only_future_spans_covered( def test_current_span_gets_attributes(self, langfuse_client, memory_exporter): """Verify the currently active span gets attributes when propagate_attributes is called.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): # Call propagate_attributes while parent-span is active with propagate_attributes(user_id="user_123"): pass @@ -335,18 +340,18 @@ def test_current_span_gets_attributes(self, langfuse_client, memory_exporter): def test_spans_outside_context_unaffected(self, langfuse_client, memory_exporter): """Verify spans created outside context don't get attributes.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): # Span before context - span1 = langfuse_client.start_span(name="span-1") + span1 = langfuse_client.start_observation(name="span-1") span1.end() # Span inside context with propagate_attributes(user_id="user_123"): - span2 = langfuse_client.start_span(name="span-2") + span2 = langfuse_client.start_observation(name="span-2") span2.end() # Span after context - span3 = langfuse_client.start_span(name="span-3") + span3 = langfuse_client.start_observation(name="span-3") span3.end() # Verify: only span2 has user_id @@ -373,9 +378,9 @@ def test_user_id_over_200_chars_dropped(self, langfuse_client, memory_exporter): """Verify user_id over 200 characters is dropped with warning.""" long_user_id = "x" * 201 - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id=long_user_id): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have user_id @@ -388,9 +393,9 @@ def test_session_id_over_200_chars_dropped(self, langfuse_client, memory_exporte """Verify session_id over 200 characters is dropped with warning.""" long_session_id = "y" * 201 - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(session_id=long_session_id): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have session_id @@ -403,9 +408,9 @@ def test_metadata_value_over_200_chars_dropped( self, langfuse_client, memory_exporter ): """Verify metadata values over 200 characters are dropped with warning.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(metadata={"key": "z" * 201}): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have metadata.key @@ -418,9 +423,9 @@ def test_exactly_200_chars_accepted(self, langfuse_client, memory_exporter): """Verify exactly 200 characters is accepted (boundary test).""" user_id_200 = "x" * 200 - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id=user_id_200): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child HAS user_id @@ -433,9 +438,9 @@ def test_201_chars_rejected(self, langfuse_client, memory_exporter): """Verify 201 characters is rejected (boundary test).""" user_id_201 = "x" * 201 - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id=user_id_201): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have user_id @@ -446,9 +451,9 @@ def test_201_chars_rejected(self, langfuse_client, memory_exporter): def test_non_string_user_id_dropped(self, langfuse_client, memory_exporter): """Verify non-string user_id is dropped with warning.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id=12345): # type: ignore - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have user_id @@ -459,7 +464,7 @@ def test_non_string_user_id_dropped(self, langfuse_client, memory_exporter): def test_mixed_valid_invalid_metadata(self, langfuse_client, memory_exporter): """Verify mixed valid/invalid metadata - valid entries kept, invalid dropped.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( metadata={ "valid_key": "valid_value", @@ -467,7 +472,7 @@ def test_mixed_valid_invalid_metadata(self, langfuse_client, memory_exporter): "another_valid": "ok", } ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify: valid keys present, invalid key absent @@ -492,15 +497,15 @@ class TestPropagateAttributesNesting(TestPropagateAttributesBase): def test_nested_contexts_inner_overwrites(self, langfuse_client, memory_exporter): """Verify inner context overwrites outer context values.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user1"): # Create span in outer context - span1 = langfuse_client.start_span(name="span-1") + span1 = langfuse_client.start_observation(name="span-1") span1.end() # Inner context with different user_id with propagate_attributes(user_id="user2"): - span2 = langfuse_client.start_span(name="span-2") + span2 = langfuse_client.start_observation(name="span-2") span2.end() # Verify: span1 has user1, span2 has user2 @@ -516,19 +521,19 @@ def test_nested_contexts_inner_overwrites(self, langfuse_client, memory_exporter def test_after_inner_context_outer_restored(self, langfuse_client, memory_exporter): """Verify outer context is restored after exiting inner context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user1"): # Span in outer context - span1 = langfuse_client.start_span(name="span-1") + span1 = langfuse_client.start_observation(name="span-1") span1.end() # Inner context with propagate_attributes(user_id="user2"): - span2 = langfuse_client.start_span(name="span-2") + span2 = langfuse_client.start_observation(name="span-2") span2.end() # Back to outer context - span3 = langfuse_client.start_span(name="span-3") + span3 = langfuse_client.start_observation(name="span-3") span3.end() # Verify: span1 and span3 have user1, span2 has user2 @@ -549,11 +554,11 @@ def test_after_inner_context_outer_restored(self, langfuse_client, memory_export def test_nested_different_attributes(self, langfuse_client, memory_exporter): """Verify nested contexts with different attributes merge correctly.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user1"): # Inner context adds session_id with propagate_attributes(session_id="session1"): - span = langfuse_client.start_span(name="span-1") + span = langfuse_client.start_observation(name="span-1") span.end() # Verify: span has BOTH user_id and session_id @@ -567,21 +572,21 @@ def test_nested_different_attributes(self, langfuse_client, memory_exporter): def test_nested_metadata_merges_additively(self, langfuse_client, memory_exporter): """Verify nested contexts merge metadata keys additively.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(metadata={"env": "prod", "region": "us-east"}): # Outer span should have outer metadata - outer_span = langfuse_client.start_span(name="outer-span") + outer_span = langfuse_client.start_observation(name="outer-span") outer_span.end() # Inner context adds more metadata with propagate_attributes( metadata={"experiment": "A", "version": "2.0"} ): - inner_span = langfuse_client.start_span(name="inner-span") + inner_span = langfuse_client.start_observation(name="inner-span") inner_span.end() # Back to outer context - after_span = langfuse_client.start_span(name="after-span") + after_span = langfuse_client.start_observation(name="after-span") after_span.end() # Verify: outer span has only outer metadata @@ -643,7 +648,7 @@ def test_nested_metadata_inner_overwrites_conflicting_keys( self, langfuse_client, memory_exporter ): """Verify nested contexts: inner metadata overwrites outer for same keys.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( metadata={"env": "staging", "version": "1.0", "region": "us-west"} ): @@ -651,7 +656,7 @@ def test_nested_metadata_inner_overwrites_conflicting_keys( with propagate_attributes( metadata={"env": "production", "experiment": "B"} ): - span = langfuse_client.start_span(name="span-1") + span = langfuse_client.start_observation(name="span-1") span.end() # Verify: inner values overwrite outer for conflicting keys @@ -685,11 +690,11 @@ def test_nested_metadata_inner_overwrites_conflicting_keys( def test_triple_nested_metadata_accumulates(self, langfuse_client, memory_exporter): """Verify metadata accumulates across three levels of nesting.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(metadata={"level": "1", "a": "outer"}): with propagate_attributes(metadata={"level": "2", "b": "middle"}): with propagate_attributes(metadata={"level": "3", "c": "inner"}): - span = langfuse_client.start_span(name="deep-span") + span = langfuse_client.start_observation(name="deep-span") span.end() # Verify: deepest span has all metadata with innermost level winning @@ -721,11 +726,11 @@ def test_triple_nested_metadata_accumulates(self, langfuse_client, memory_export def test_metadata_merge_with_empty_inner(self, langfuse_client, memory_exporter): """Verify empty inner metadata dict doesn't clear outer metadata.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(metadata={"key1": "value1", "key2": "value2"}): # Inner context with empty metadata with propagate_attributes(metadata={}): - span = langfuse_client.start_span(name="span-1") + span = langfuse_client.start_observation(name="span-1") span.end() # Verify: outer metadata is preserved @@ -745,14 +750,14 @@ def test_metadata_merge_preserves_user_session( self, langfuse_client, memory_exporter ): """Verify metadata merging doesn't affect user_id/session_id.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( user_id="user1", session_id="session1", metadata={"outer": "value"}, ): with propagate_attributes(metadata={"inner": "value"}): - span = langfuse_client.start_span(name="span-1") + span = langfuse_client.start_observation(name="span-1") span.end() # Verify: user_id and session_id are preserved, metadata merged @@ -780,9 +785,9 @@ class TestPropagateAttributesEdgeCases(TestPropagateAttributesBase): def test_propagate_attributes_with_no_args(self, langfuse_client, memory_exporter): """Verify calling propagate_attributes() with no args doesn't error.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Should not crash, spans created normally @@ -791,9 +796,9 @@ def test_propagate_attributes_with_no_args(self, langfuse_client, memory_exporte def test_none_values_ignored(self, langfuse_client, memory_exporter): """Verify None values are ignored without error.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id=None, session_id=None, metadata=None): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Should not crash, no attributes set @@ -807,9 +812,9 @@ def test_none_values_ignored(self, langfuse_client, memory_exporter): def test_empty_metadata_dict(self, langfuse_client, memory_exporter): """Verify empty metadata dict doesn't cause errors.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(metadata={}): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Should not crash, no metadata attributes set @@ -818,14 +823,14 @@ def test_empty_metadata_dict(self, langfuse_client, memory_exporter): def test_all_invalid_metadata_values(self, langfuse_client, memory_exporter): """Verify all invalid metadata values results in no metadata attributes.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( metadata={ "key1": "x" * 201, # Too long "key2": "y" * 201, # Too long } ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # No metadata attributes should be set @@ -842,7 +847,7 @@ def test_propagate_with_no_active_span(self, langfuse_client, memory_exporter): # Call propagate_attributes without creating a parent span first with propagate_attributes(user_id="user_123"): # Now create a span - with langfuse_client.start_as_current_span(name="span-1"): + with langfuse_client.start_as_current_observation(name="span-1"): pass # Should not crash, span should have user_id @@ -859,9 +864,9 @@ def test_user_id_uses_correct_attribute_name( self, langfuse_client, memory_exporter ): """Verify user_id uses the correct OTel attribute name.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(user_id="user_123"): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -876,9 +881,9 @@ def test_session_id_uses_correct_attribute_name( self, langfuse_client, memory_exporter ): """Verify session_id uses the correct OTel attribute name.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(session_id="session_abc"): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -891,11 +896,11 @@ def test_session_id_uses_correct_attribute_name( def test_metadata_keys_properly_prefixed(self, langfuse_client, memory_exporter): """Verify metadata keys are properly prefixed with TRACE_METADATA.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( metadata={"experiment": "A", "version": "1.0", "env": "prod"} ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -913,9 +918,9 @@ def test_metadata_keys_properly_prefixed(self, langfuse_client, memory_exporter) def test_multiple_metadata_keys_independent(self, langfuse_client, memory_exporter): """Verify multiple metadata keys are stored as independent attributes.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(metadata={"k1": "v1", "k2": "v2", "k3": "v3"}): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -945,11 +950,11 @@ def test_propagation_with_threadpoolexecutor( def worker_function(span_name: str): """Worker creates a span in thread pool.""" - span = langfuse_client.start_span(name=span_name) + span = langfuse_client.start_observation(name=span_name) span.end() return span_name - with langfuse_client.start_as_current_span(name="main-span"): + with langfuse_client.start_as_current_observation(name="main-span"): with propagate_attributes(user_id="main_user", session_id="main_session"): # Execute work in thread pool with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: @@ -980,9 +985,9 @@ def test_propagation_isolated_between_threads( def create_trace_with_user(user_id: str): """Create a trace with specific user_id.""" - with langfuse_client.start_as_current_span(name=f"trace-{user_id}"): + with langfuse_client.start_as_current_observation(name=f"trace-{user_id}"): with propagate_attributes(user_id=user_id): - span = langfuse_client.start_span(name=f"span-{user_id}") + span = langfuse_client.start_observation(name=f"span-{user_id}") span.end() # Run two traces concurrently with different user_ids @@ -1009,13 +1014,13 @@ def test_nested_propagation_across_thread_boundary( def worker_creates_child(): """Worker thread creates a child span.""" - child = langfuse_client.start_span(name="worker-child-span") + child = langfuse_client.start_observation(name="worker-child-span") child.end() - with langfuse_client.start_as_current_span(name="main-parent-span"): + with langfuse_client.start_as_current_observation(name="main-parent-span"): with propagate_attributes(user_id="main_user"): # Create span in main thread - main_child = langfuse_client.start_span(name="main-child-span") + main_child = langfuse_client.start_observation(name="main-child-span") main_child.end() # Create span in worker thread @@ -1042,13 +1047,13 @@ def test_worker_thread_can_override_propagated_attrs( def worker_overrides_user(): """Worker thread sets its own user_id.""" with propagate_attributes(user_id="worker_user"): - span = langfuse_client.start_span(name="worker-span") + span = langfuse_client.start_observation(name="worker-span") span.end() - with langfuse_client.start_as_current_span(name="main-span"): + with langfuse_client.start_as_current_observation(name="main-span"): with propagate_attributes(user_id="main_user"): # Create span in main thread - main_span = langfuse_client.start_span(name="main-child-span") + main_span = langfuse_client.start_observation(name="main-child-span") main_span.end() # Worker overrides with its own user_id @@ -1074,10 +1079,10 @@ def test_multiple_workers_with_same_propagated_context( def worker_function(worker_id: int): """Worker creates a span.""" - span = langfuse_client.start_span(name=f"worker-{worker_id}") + span = langfuse_client.start_observation(name=f"worker-{worker_id}") span.end() - with langfuse_client.start_as_current_span(name="main-span"): + with langfuse_client.start_as_current_observation(name="main-span"): with propagate_attributes(session_id="shared_session"): # Submit 5 workers with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: @@ -1100,9 +1105,9 @@ def test_concurrent_traces_with_different_attributes( def create_trace(trace_id: int): """Create a trace with unique user_id.""" - with langfuse_client.start_as_current_span(name=f"trace-{trace_id}"): + with langfuse_client.start_as_current_observation(name=f"trace-{trace_id}"): with propagate_attributes(user_id=f"user_{trace_id}"): - span = langfuse_client.start_span(name=f"span-{trace_id}") + span = langfuse_client.start_observation(name=f"span-{trace_id}") span.end() # Create 10 traces concurrently @@ -1124,14 +1129,14 @@ def test_exception_in_worker_preserves_context( def worker_raises_exception(): """Worker creates span then raises exception.""" - span = langfuse_client.start_span(name="worker-span") + span = langfuse_client.start_observation(name="worker-span") span.end() raise ValueError("Test exception") - with langfuse_client.start_as_current_span(name="main-span"): + with langfuse_client.start_as_current_observation(name="main-span"): with propagate_attributes(user_id="main_user"): # Create span before worker - span1 = langfuse_client.start_span(name="span-before") + span1 = langfuse_client.start_observation(name="span-before") span1.end() # Worker raises exception (catch it) @@ -1143,7 +1148,7 @@ def worker_raises_exception(): pass # Expected # Create span after exception - span2 = langfuse_client.start_span(name="span-after") + span2 = langfuse_client.start_observation(name="span-after") span2.end() # Verify both main thread spans still have correct user_id @@ -1168,10 +1173,10 @@ def test_different_tracer_spans_get_attributes( # Get a different tracer (not the Langfuse tracer) other_tracer = tracer_provider.get_tracer("other-library", "1.0.0") - with langfuse_client.start_as_current_span(name="langfuse-parent"): + with langfuse_client.start_as_current_observation(name="langfuse-parent"): with propagate_attributes(user_id="user_123", session_id="session_abc"): # Create span with Langfuse tracer - langfuse_span = langfuse_client.start_span(name="langfuse-child") + langfuse_span = langfuse_client.start_observation(name="langfuse-child") langfuse_span.end() # Create span with different tracer @@ -1210,14 +1215,16 @@ def test_nested_spans_from_multiple_tracers( tracer_a = tracer_provider.get_tracer("library-a", "1.0.0") tracer_b = tracer_provider.get_tracer("library-b", "2.0.0") - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): with propagate_attributes( user_id="user_123", metadata={"experiment": "cross_tracer"} ): # Create nested spans from different tracers with tracer_a.start_as_current_span(name="library-a-span"): with tracer_b.start_as_current_span(name="library-b-span"): - langfuse_leaf = langfuse_client.start_span(name="langfuse-leaf") + langfuse_leaf = langfuse_client.start_observation( + name="langfuse-leaf" + ) langfuse_leaf.end() # Verify all spans have the attributes @@ -1240,7 +1247,7 @@ def test_other_tracer_span_before_propagate_context( """Verify spans created before propagate_attributes don't get attributes.""" other_tracer = tracer_provider.get_tracer("other-library", "1.0.0") - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): # Create span BEFORE propagate_attributes with other_tracer.start_as_current_span(name="span-before"): pass @@ -1268,7 +1275,7 @@ def test_mixed_tracers_with_metadata( """Verify metadata propagates correctly to spans from different tracers.""" other_tracer = tracer_provider.get_tracer("instrumented-library", "1.0.0") - with langfuse_client.start_as_current_span(name="main"): + with langfuse_client.start_as_current_observation(name="main"): with propagate_attributes( metadata={ "env": "production", @@ -1277,7 +1284,9 @@ def test_mixed_tracers_with_metadata( } ): # Create spans from both tracers - langfuse_span = langfuse_client.start_span(name="langfuse-operation") + langfuse_span = langfuse_client.start_observation( + name="langfuse-operation" + ) langfuse_span.end() with other_tracer.start_as_current_span(name="library-operation"): @@ -1315,7 +1324,9 @@ def test_propagate_without_langfuse_parent( with other_tracer.start_as_current_span(name="other-child"): pass - langfuse_child = langfuse_client.start_span(name="langfuse-child") + langfuse_child = langfuse_client.start_observation( + name="langfuse-child" + ) langfuse_child.end() # Verify all spans have attributes (including non-Langfuse parent) @@ -1340,7 +1351,7 @@ def test_attributes_persist_across_tracer_changes( tracer_2 = tracer_provider.get_tracer("library-2", "1.0.0") tracer_3 = tracer_provider.get_tracer("library-3", "1.0.0") - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): with propagate_attributes(user_id="persistent_user"): # Bounce between different tracers with tracer_1.start_as_current_span(name="step-1"): @@ -1350,7 +1361,7 @@ def test_attributes_persist_across_tracer_changes( with tracer_3.start_as_current_span(name="step-3"): pass - langfuse_span = langfuse_client.start_span(name="step-4") + langfuse_span = langfuse_client.start_observation(name="step-4") langfuse_span.end() # Verify all steps have the user_id @@ -1372,10 +1383,10 @@ async def test_async_propagation_basic(self, langfuse_client, memory_exporter): async def async_operation(): """Async function that creates a span.""" - span = langfuse_client.start_span(name="async-span") + span = langfuse_client.start_observation(name="async-span") span.end() - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes(user_id="async_user", session_id="async_session"): await async_operation() @@ -1393,20 +1404,20 @@ async def test_async_nested_operations(self, langfuse_client, memory_exporter): """Verify attributes propagate through nested async operations.""" async def level_3(): - span = langfuse_client.start_span(name="level-3-span") + span = langfuse_client.start_observation(name="level-3-span") span.end() async def level_2(): - span = langfuse_client.start_span(name="level-2-span") + span = langfuse_client.start_observation(name="level-2-span") span.end() await level_3() async def level_1(): - span = langfuse_client.start_span(name="level-1-span") + span = langfuse_client.start_observation(name="level-1-span") span.end() await level_2() - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): with propagate_attributes( user_id="nested_user", metadata={"level": "nested"} ): @@ -1427,10 +1438,10 @@ async def level_1(): @pytest.mark.asyncio async def test_async_context_manager(self, langfuse_client, memory_exporter): """Verify propagate_attributes works as context manager in async function.""" - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): # propagate_attributes supports both sync and async contexts via regular 'with' with propagate_attributes(user_id="async_ctx_user"): - span = langfuse_client.start_span(name="inside-async-ctx") + span = langfuse_client.start_observation(name="inside-async-ctx") span.end() span_data = self.get_span_by_name(memory_exporter, "inside-async-ctx") @@ -1447,10 +1458,10 @@ async def test_multiple_async_tasks_concurrent( async def create_trace_with_user(user_id: str): """Create a trace with specific user_id.""" - with langfuse_client.start_as_current_span(name=f"trace-{user_id}"): + with langfuse_client.start_as_current_observation(name=f"trace-{user_id}"): with propagate_attributes(user_id=user_id): await asyncio.sleep(0.01) # Simulate async work - span = langfuse_client.start_span(name=f"span-{user_id}") + span = langfuse_client.start_observation(name=f"span-{user_id}") span.end() # Run multiple traces concurrently @@ -1473,16 +1484,16 @@ async def test_async_with_sync_nested(self, langfuse_client, memory_exporter): def sync_operation(): """Sync function called from async context.""" - span = langfuse_client.start_span(name="sync-in-async") + span = langfuse_client.start_observation(name="sync-in-async") span.end() async def async_operation(): """Async function that calls sync code.""" - span1 = langfuse_client.start_span(name="async-span") + span1 = langfuse_client.start_observation(name="async-span") span1.end() sync_operation() - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): with propagate_attributes(user_id="mixed_user"): await async_operation() @@ -1505,13 +1516,13 @@ async def test_async_exception_preserves_context( async def failing_operation(): """Async operation that raises exception.""" - span = langfuse_client.start_span(name="span-before-error") + span = langfuse_client.start_observation(name="span-before-error") span.end() raise ValueError("Test error") - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): with propagate_attributes(user_id="error_user"): - span1 = langfuse_client.start_span(name="span-before-async") + span1 = langfuse_client.start_observation(name="span-before-async") span1.end() try: @@ -1519,7 +1530,7 @@ async def failing_operation(): except ValueError: pass # Expected - span2 = langfuse_client.start_span(name="span-after-error") + span2 = langfuse_client.start_observation(name="span-after-error") span2.end() # Verify all spans have attributes @@ -1534,10 +1545,10 @@ async def test_async_with_metadata(self, langfuse_client, memory_exporter): """Verify metadata propagates correctly in async context.""" async def async_with_metadata(): - span = langfuse_client.start_span(name="async-metadata-span") + span = langfuse_client.start_observation(name="async-metadata-span") span.end() - with langfuse_client.start_as_current_span(name="root"): + with langfuse_client.start_as_current_observation(name="root"): with propagate_attributes( user_id="metadata_user", metadata={"async": "true", "operation": "test"}, @@ -1568,7 +1579,7 @@ def test_baggage_is_set_when_as_baggage_true(self, langfuse_client): from opentelemetry import baggage from opentelemetry import context as otel_context - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( user_id="user_123", session_id="session_abc", @@ -1596,7 +1607,7 @@ def test_spans_receive_attributes_from_baggage( self, langfuse_client, memory_exporter ): """Verify child spans get attributes when parent uses as_baggage=True.""" - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( user_id="baggage_user", session_id="baggage_session", @@ -1604,7 +1615,7 @@ def test_spans_receive_attributes_from_baggage( as_baggage=True, ): # Create child span - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child span has all attributes @@ -1628,7 +1639,7 @@ def test_baggage_disabled_by_default(self, langfuse_client): from opentelemetry import baggage from opentelemetry import context as otel_context - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( user_id="user_123", session_id="session_abc", @@ -1642,12 +1653,12 @@ def test_metadata_key_with_user_id_substring_doesnt_collide( self, langfuse_client, memory_exporter ): """Verify metadata key containing 'user_id' substring doesn't map to TRACE_USER_ID.""" - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( metadata={"user_info": "some_data", "user_id_copy": "another"}, as_baggage=True, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -1673,12 +1684,12 @@ def test_metadata_key_with_session_substring_doesnt_collide( self, langfuse_client, memory_exporter ): """Verify metadata key containing 'session_id' substring doesn't map to TRACE_SESSION_ID.""" - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( metadata={"session_data": "value1", "session_id_backup": "value2"}, as_baggage=True, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -1704,7 +1715,7 @@ def test_metadata_keys_extract_correctly_from_baggage( self, langfuse_client, memory_exporter ): """Verify metadata keys are correctly formatted in baggage and extracted back.""" - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( metadata={ "env": "production", @@ -1713,7 +1724,7 @@ def test_metadata_keys_extract_correctly_from_baggage( }, as_baggage=True, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -1737,7 +1748,7 @@ def test_metadata_keys_extract_correctly_from_baggage( def test_baggage_and_context_both_propagate(self, langfuse_client, memory_exporter): """Verify attributes propagate when both baggage and context mechanisms are active.""" - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): # Enable baggage with propagate_attributes( user_id="user_both", @@ -1746,8 +1757,8 @@ def test_baggage_and_context_both_propagate(self, langfuse_client, memory_export as_baggage=True, ): # Create multiple levels of nesting - with langfuse_client.start_as_current_span(name="middle"): - child = langfuse_client.start_span(name="leaf") + with langfuse_client.start_as_current_observation(name="middle"): + child = langfuse_client.start_observation(name="leaf") child.end() # Verify all spans have attributes @@ -1770,7 +1781,7 @@ def test_baggage_survives_context_isolation(self, langfuse_client, memory_export from opentelemetry import context as otel_context # Step 1: Create context with baggage - with langfuse_client.start_as_current_span(name="original-process"): + with langfuse_client.start_as_current_observation(name="original-process"): with propagate_attributes( user_id="cross_process_user", session_id="cross_process_session", @@ -1783,8 +1794,8 @@ def test_baggage_survives_context_isolation(self, langfuse_client, memory_export # This mimics what happens when receiving an HTTP request with baggage headers token = otel_context.attach(context_with_baggage) try: - with langfuse_client.start_as_current_span(name="remote-process"): - child = langfuse_client.start_span(name="remote-child") + with langfuse_client.start_as_current_observation(name="remote-process"): + child = langfuse_client.start_observation(name="remote-child") child.end() finally: otel_context.detach(token) @@ -1808,12 +1819,12 @@ class TestPropagateAttributesVersion(TestPropagateAttributesBase): def test_version_propagates_to_child_spans(self, langfuse_client, memory_exporter): """Verify version propagates to all child spans within context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version="v1.2.3"): - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() # Verify both children have version @@ -1833,13 +1844,13 @@ def test_version_propagates_to_child_spans(self, langfuse_client, memory_exporte def test_version_with_user_and_session(self, langfuse_client, memory_exporter): """Verify version works together with user_id and session_id.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( user_id="user_123", session_id="session_abc", version="2.0.0", ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has all attributes @@ -1856,12 +1867,12 @@ def test_version_with_user_and_session(self, langfuse_client, memory_exporter): def test_version_with_metadata(self, langfuse_client, memory_exporter): """Verify version works together with metadata.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( version="1.0.0", metadata={"env": "production", "region": "us-east"}, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -1883,9 +1894,9 @@ def test_version_validation_over_200_chars(self, langfuse_client, memory_exporte """Verify version over 200 characters is dropped with warning.""" long_version = "v" + "1.0.0" * 50 # Create a very long version string - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version=long_version): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have version @@ -1896,9 +1907,9 @@ def test_version_exactly_200_chars(self, langfuse_client, memory_exporter): """Verify exactly 200 character version is accepted.""" version_200 = "v" * 200 - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version=version_200): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child HAS version @@ -1911,19 +1922,19 @@ def test_version_nested_contexts_inner_overwrites( self, langfuse_client, memory_exporter ): """Verify inner context overwrites outer version.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version="1.0.0"): # Create span in outer context - span1 = langfuse_client.start_span(name="span-1") + span1 = langfuse_client.start_observation(name="span-1") span1.end() # Inner context with different version with propagate_attributes(version="2.0.0"): - span2 = langfuse_client.start_span(name="span-2") + span2 = langfuse_client.start_observation(name="span-2") span2.end() # Back to outer context - span3 = langfuse_client.start_span(name="span-3") + span3 = langfuse_client.start_observation(name="span-3") span3.end() # Verify: span1 and span3 have version 1.0.0, span2 has 2.0.0 @@ -1944,13 +1955,13 @@ def test_version_nested_contexts_inner_overwrites( def test_version_with_baggage(self, langfuse_client, memory_exporter): """Verify version propagates through baggage.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( version="baggage_version", user_id="user_123", as_baggage=True, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has version @@ -1975,10 +1986,10 @@ def test_version_semantic_versioning_formats( "0.1.0", ] - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): for idx, version in enumerate(test_versions): with propagate_attributes(version=version): - span = langfuse_client.start_span(name=f"span-{idx}") + span = langfuse_client.start_observation(name=f"span-{idx}") span.end() # Verify all versions are correctly set @@ -1990,9 +2001,9 @@ def test_version_semantic_versioning_formats( def test_version_non_string_dropped(self, langfuse_client, memory_exporter): """Verify non-string version is dropped with warning.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version=123): # type: ignore - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have version @@ -2003,10 +2014,12 @@ def test_version_propagates_to_grandchildren( self, langfuse_client, memory_exporter ): """Verify version propagates through multiple levels of nesting.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version="nested_v1"): - with langfuse_client.start_as_current_span(name="child-span"): - grandchild = langfuse_client.start_span(name="grandchild-span") + with langfuse_client.start_as_current_observation(name="child-span"): + grandchild = langfuse_client.start_observation( + name="grandchild-span" + ) grandchild.end() # Verify all three levels have version @@ -2024,10 +2037,10 @@ async def test_version_with_async(self, langfuse_client, memory_exporter): """Verify version propagates in async context.""" async def async_operation(): - span = langfuse_client.start_span(name="async-span") + span = langfuse_client.start_observation(name="async-span") span.end() - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes(version="async_v1.0"): await async_operation() @@ -2038,9 +2051,9 @@ async def async_operation(): def test_version_attribute_key_format(self, langfuse_client, memory_exporter): """Verify version uses correct attribute key format.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(version="key_test_v1"): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2056,12 +2069,12 @@ class TestPropagateAttributesTags(TestPropagateAttributesBase): def test_tags_propagate_to_child_spans(self, langfuse_client, memory_exporter): """Verify tags propagate to all child spans within context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=["production", "api-v2", "critical"]): - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() # Verify both children have tags @@ -2081,9 +2094,9 @@ def test_tags_propagate_to_child_spans(self, langfuse_client, memory_exporter): def test_tags_with_single_tag(self, langfuse_client, memory_exporter): """Verify single tag works correctly.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=["experiment"]): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2095,9 +2108,9 @@ def test_tags_with_single_tag(self, langfuse_client, memory_exporter): def test_empty_tags_list(self, langfuse_client, memory_exporter): """Verify empty tags list is handled correctly.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=[]): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # With empty list, tags should not be set @@ -2106,13 +2119,13 @@ def test_empty_tags_list(self, langfuse_client, memory_exporter): def test_tags_with_user_and_session(self, langfuse_client, memory_exporter): """Verify tags work together with user_id and session_id.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( user_id="user_123", session_id="session_abc", tags=["test", "debug"], ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has all attributes @@ -2131,12 +2144,12 @@ def test_tags_with_user_and_session(self, langfuse_client, memory_exporter): def test_tags_with_metadata(self, langfuse_client, memory_exporter): """Verify tags work together with metadata.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( tags=["experiment-a", "variant-1"], metadata={"env": "staging"}, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2155,9 +2168,9 @@ def test_tags_validation_with_invalid_tag(self, langfuse_client, memory_exporter """Verify tags with one invalid entry drops all tags.""" long_tag = "x" * 201 # Over 200 chars - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=["valid_tag", long_tag]): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2167,19 +2180,19 @@ def test_tags_validation_with_invalid_tag(self, langfuse_client, memory_exporter def test_tags_nested_contexts_inner_appends(self, langfuse_client, memory_exporter): """Verify inner context appends to outer tags.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=["outer", "tag1"]): # Create span in outer context - span1 = langfuse_client.start_span(name="span-1") + span1 = langfuse_client.start_observation(name="span-1") span1.end() # Inner context with more tags with propagate_attributes(tags=["inner", "tag2"]): - span2 = langfuse_client.start_span(name="span-2") + span2 = langfuse_client.start_observation(name="span-2") span2.end() # Back to outer context - span3 = langfuse_client.start_span(name="span-3") + span3 = langfuse_client.start_observation(name="span-3") span3.end() # Verify: span1 and span3 have outer tags, span2 has inner tags @@ -2209,12 +2222,12 @@ def test_tags_nested_contexts_inner_appends(self, langfuse_client, memory_export def test_tags_with_baggage(self, langfuse_client, memory_exporter): """Verify tags propagate through baggage.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( tags=["baggage_tag1", "baggage_tag2"], as_baggage=True, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has tags @@ -2227,10 +2240,12 @@ def test_tags_with_baggage(self, langfuse_client, memory_exporter): def test_tags_propagate_to_grandchildren(self, langfuse_client, memory_exporter): """Verify tags propagate through multiple levels of nesting.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=["level1", "level2", "level3"]): - with langfuse_client.start_as_current_span(name="child-span"): - grandchild = langfuse_client.start_span(name="grandchild-span") + with langfuse_client.start_as_current_observation(name="child-span"): + grandchild = langfuse_client.start_observation( + name="grandchild-span" + ) grandchild.end() # Verify all three levels have tags @@ -2250,10 +2265,10 @@ async def test_tags_with_async(self, langfuse_client, memory_exporter): """Verify tags propagate in async context.""" async def async_operation(): - span = langfuse_client.start_span(name="async-span") + span = langfuse_client.start_observation(name="async-span") span.end() - with langfuse_client.start_as_current_span(name="parent"): + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes(tags=["async", "test"]): await async_operation() @@ -2264,9 +2279,9 @@ async def async_operation(): def test_tags_attribute_key_format(self, langfuse_client, memory_exporter): """Verify tags use correct attribute key format.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(tags=["key_test"]): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2296,10 +2311,10 @@ def test_experiment_attributes_propagate_without_dataset( # Task function that creates child spans def task_with_child_spans(*, item, **kwargs): # Create child spans to verify propagation - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() return f"processed: {item.get('input') if isinstance(item, dict) else item.input}" @@ -2437,16 +2452,10 @@ def test_experiment_attributes_propagate_with_dataset( self, langfuse_client, memory_exporter, monkeypatch ): """Test experiment attribute propagation with Langfuse dataset.""" - import time - from datetime import datetime - - from langfuse._client.attributes import _serialize - from langfuse._client.datasets import DatasetClient, DatasetItemClient - from langfuse.model import Dataset, DatasetItem, DatasetStatus # Mock the async API to create dataset run items async def mock_create_dataset_run_item(*args, **kwargs): - from langfuse.api.resources.dataset_run_items.types import DatasetRunItem + from langfuse.api import DatasetRunItem request = kwargs.get("request") return DatasetRunItem( @@ -2491,15 +2500,18 @@ async def mock_create_dataset_run_item(*args, **kwargs): ) # Create dataset client with items - dataset_item_client = DatasetItemClient(mock_dataset_item, langfuse_client) - dataset = DatasetClient(mock_dataset, [dataset_item_client]) + dataset = DatasetClient( + dataset=mock_dataset, + items=[mock_dataset_item], + langfuse_client=langfuse_client, + ) # Task with child spans def task_with_children(*, item, **kwargs): - child1 = langfuse_client.start_span(name="dataset-child-1") + child1 = langfuse_client.start_observation(name="dataset-child-1") child1.end() - child2 = langfuse_client.start_span(name="dataset-child-2") + child2 = langfuse_client.start_observation(name="dataset-child-2") child2.end() return f"Capital: {item.expected_output}" @@ -2622,9 +2634,11 @@ def test_experiment_attributes_propagate_to_nested_children( # Task with deeply nested spans def task_with_nested_spans(*, item, **kwargs): - with langfuse_client.start_as_current_span(name="child-span"): - with langfuse_client.start_as_current_span(name="grandchild-span"): - great_grandchild = langfuse_client.start_span( + with langfuse_client.start_as_current_observation(name="child-span"): + with langfuse_client.start_as_current_observation( + name="grandchild-span" + ): + great_grandchild = langfuse_client.start_observation( name="great-grandchild-span" ) great_grandchild.end() @@ -2723,7 +2737,7 @@ def test_experiment_metadata_merging(self, langfuse_client, memory_exporter): ] def task_with_child(*, item, **kwargs): - child = langfuse_client.start_span(name="metadata-child") + child = langfuse_client.start_observation(name="metadata-child") child.end() return "result" @@ -2778,12 +2792,12 @@ def test_trace_name_propagates_to_child_spans( self, langfuse_client, memory_exporter ): """Verify trace_name propagates to all child spans within context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name="my-trace-name"): - child1 = langfuse_client.start_span(name="child-span-1") + child1 = langfuse_client.start_observation(name="child-span-1") child1.end() - child2 = langfuse_client.start_span(name="child-span-2") + child2 = langfuse_client.start_observation(name="child-span-2") child2.end() # Verify both children have trace_name @@ -2805,10 +2819,12 @@ def test_trace_name_propagates_to_grandchildren( self, langfuse_client, memory_exporter ): """Verify trace_name propagates through multiple levels of nesting.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name="nested-trace"): - with langfuse_client.start_as_current_span(name="child-span"): - grandchild = langfuse_client.start_span(name="grandchild-span") + with langfuse_client.start_as_current_observation(name="child-span"): + grandchild = langfuse_client.start_observation( + name="grandchild-span" + ) grandchild.end() # Verify all three levels have trace_name @@ -2823,13 +2839,13 @@ def test_trace_name_propagates_to_grandchildren( def test_trace_name_with_user_and_session(self, langfuse_client, memory_exporter): """Verify trace_name works together with user_id and session_id.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( user_id="user_123", session_id="session_abc", trace_name="combined-trace", ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has all attributes @@ -2846,12 +2862,12 @@ def test_trace_name_with_user_and_session(self, langfuse_client, memory_exporter def test_trace_name_with_version(self, langfuse_client, memory_exporter): """Verify trace_name works together with version.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( trace_name="versioned-trace", version="1.0.0", ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2864,12 +2880,12 @@ def test_trace_name_with_version(self, langfuse_client, memory_exporter): def test_trace_name_with_metadata(self, langfuse_client, memory_exporter): """Verify trace_name works together with metadata.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( trace_name="metadata-trace", metadata={"env": "production", "region": "us-east"}, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() child_span = self.get_span_by_name(memory_exporter, "child-span") @@ -2893,9 +2909,9 @@ def test_trace_name_validation_over_200_chars( """Verify trace_name over 200 characters is dropped with warning.""" long_name = "trace-" + "a" * 200 # Create a very long trace name - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name=long_name): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have trace_name @@ -2906,9 +2922,9 @@ def test_trace_name_exactly_200_chars(self, langfuse_client, memory_exporter): """Verify exactly 200 character trace_name is accepted.""" trace_name_200 = "t" * 200 - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name=trace_name_200): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child HAS trace_name @@ -2921,19 +2937,19 @@ def test_trace_name_nested_contexts_inner_overwrites( self, langfuse_client, memory_exporter ): """Verify inner context overwrites outer trace_name.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name="outer-trace"): # Create span in outer context - span1 = langfuse_client.start_span(name="span-1") + span1 = langfuse_client.start_observation(name="span-1") span1.end() # Inner context with different trace_name with propagate_attributes(trace_name="inner-trace"): - span2 = langfuse_client.start_span(name="span-2") + span2 = langfuse_client.start_observation(name="span-2") span2.end() # Back to outer context - span3 = langfuse_client.start_span(name="span-3") + span3 = langfuse_client.start_observation(name="span-3") span3.end() # Verify: span1 and span3 have outer-trace, span2 has inner-trace @@ -2954,7 +2970,7 @@ def test_trace_name_nested_contexts_inner_overwrites( def test_trace_name_sets_on_current_span(self, langfuse_client, memory_exporter): """Verify trace_name is set on the current span when entering context.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name="current-trace"): pass # Just enter and exit context @@ -2966,9 +2982,9 @@ def test_trace_name_sets_on_current_span(self, langfuse_client, memory_exporter) def test_trace_name_non_string_dropped(self, langfuse_client, memory_exporter): """Verify non-string trace_name is dropped with warning.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes(trace_name=123): # type: ignore - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child does NOT have trace_name @@ -2977,13 +2993,13 @@ def test_trace_name_non_string_dropped(self, langfuse_client, memory_exporter): def test_trace_name_with_baggage(self, langfuse_client, memory_exporter): """Verify trace_name propagates through baggage.""" - with langfuse_client.start_as_current_span(name="parent-span"): + with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( trace_name="baggage-trace", user_id="user_123", as_baggage=True, ): - child = langfuse_client.start_span(name="child-span") + child = langfuse_client.start_observation(name="child-span") child.end() # Verify child has trace_name diff --git a/tests/test_resource_manager.py b/tests/test_resource_manager.py index fa6eb56bf..c692db5ab 100644 --- a/tests/test_resource_manager.py +++ b/tests/test_resource_manager.py @@ -10,12 +10,16 @@ def test_get_client_preserves_all_settings(): with LangfuseResourceManager._lock: LangfuseResourceManager._instances.clear() + def should_export(span): + return span.name != "drop" + settings = { "environment": "test-env", "release": "v1.2.3", "timeout": 30, "flush_at": 100, "sample_rate": 0.8, + "should_export_span": should_export, "additional_headers": {"X-Custom": "value"}, } @@ -29,6 +33,7 @@ def test_get_client_preserves_all_settings(): assert rm.environment == settings["environment"] assert rm.timeout == settings["timeout"] assert rm.sample_rate == settings["sample_rate"] + assert rm.should_export_span is should_export assert rm.additional_headers == settings["additional_headers"] original_client.shutdown() @@ -36,6 +41,13 @@ def test_get_client_preserves_all_settings(): def test_get_client_multiple_clients_preserve_different_settings(): """Test that get_client() preserves different settings for multiple clients.""" + + def should_export_a(span): + return span.name.startswith("a") + + def should_export_b(span): + return span.name.startswith("b") + # Settings for client A settings_a = { "public_key": "pk-comprehensive-a", @@ -44,6 +56,7 @@ def test_get_client_multiple_clients_preserve_different_settings(): "release": "release-a", "timeout": 10, "sample_rate": 0.5, + "should_export_span": should_export_a, } # Settings for client B @@ -54,6 +67,7 @@ def test_get_client_multiple_clients_preserve_different_settings(): "release": "release-b", "timeout": 20, "sample_rate": 0.9, + "should_export_span": should_export_b, } client_a = Langfuse(**settings_a) @@ -74,6 +88,8 @@ def test_get_client_multiple_clients_preserve_different_settings(): assert retrieved_b._resources.sample_rate == settings_b["sample_rate"] assert retrieved_a._resources.release == settings_a["release"] assert retrieved_b._resources.release == settings_b["release"] + assert retrieved_a._resources.should_export_span is should_export_a + assert retrieved_b._resources.should_export_span is should_export_b client_a.shutdown() client_b.shutdown() diff --git a/tests/test_span_filter.py b/tests/test_span_filter.py new file mode 100644 index 000000000..94f6ba4f8 --- /dev/null +++ b/tests/test_span_filter.py @@ -0,0 +1,112 @@ +"""Tests for span filter predicates used by the Langfuse span processor.""" + +from types import SimpleNamespace +from typing import Any, Optional + +from langfuse.span_filter import ( + is_default_export_span, + is_genai_span, + is_known_llm_instrumentor, + is_langfuse_span, +) + + +def _make_span( + *, + scope_name: Optional[str] = None, + attributes: Optional[dict[Any, Any]] = None, +): + scope = None if scope_name is None else SimpleNamespace(name=scope_name) + return SimpleNamespace(instrumentation_scope=scope, attributes=attributes) + + +def test_is_langfuse_span_true(): + """Return true for a Langfuse SDK instrumentation scope.""" + assert is_langfuse_span(_make_span(scope_name="langfuse-sdk")) is True + + +def test_is_langfuse_span_false(): + """Return false for non-Langfuse instrumentation scopes.""" + assert is_langfuse_span(_make_span(scope_name="other-lib")) is False + + +def test_is_langfuse_span_no_scope(): + """Return false when instrumentation scope is missing.""" + assert is_langfuse_span(_make_span(scope_name=None)) is False + + +def test_is_genai_span_with_genai_attributes(): + """Return true when span attributes include a gen_ai.* key.""" + assert ( + is_genai_span( + _make_span( + attributes={"gen_ai.request.model": "gpt-4o", "http.method": "POST"} + ) + ) + is True + ) + + +def test_is_genai_span_ignores_non_string_keys(): + """Ignore non-string keys when checking gen_ai.* attributes.""" + assert ( + is_genai_span(_make_span(attributes={1: "value", "http.method": "POST"})) + is False + ) + + +def test_is_genai_span_no_attributes(): + """Return false when attributes are missing.""" + assert is_genai_span(_make_span(attributes=None)) is False + + +def test_is_known_llm_instrumentor_exact_match(): + """Return true for exact allowlisted scope names.""" + assert is_known_llm_instrumentor(_make_span(scope_name="ai")) is True + + +def test_is_known_llm_instrumentor_prefix_match(): + """Return true for allowlisted namespace scope descendants.""" + assert ( + is_known_llm_instrumentor( + _make_span(scope_name="openinference.instrumentation.agno.worker") + ) + is True + ) + + +def test_is_known_llm_instrumentor_unknown(): + """Return false for unknown instrumentation scopes.""" + assert is_known_llm_instrumentor(_make_span(scope_name="unknown.scope")) is False + + +def test_is_default_export_span_langfuse(): + """Export Langfuse spans with the default filter.""" + assert is_default_export_span(_make_span(scope_name="langfuse-sdk")) is True + + +def test_is_default_export_span_genai(): + """Export gen_ai spans with the default filter.""" + assert ( + is_default_export_span( + _make_span( + scope_name="unknown.scope", attributes={"gen_ai.prompt": "hello"} + ) + ) + is True + ) + + +def test_is_default_export_span_known_scope(): + """Export known instrumentation scopes with the default filter.""" + assert is_default_export_span(_make_span(scope_name="langsmith")) is True + + +def test_is_default_export_span_rejects_unknown(): + """Reject unknown scopes without gen_ai attributes in default filter.""" + assert ( + is_default_export_span( + _make_span(scope_name="unknown.scope", attributes={"http.method": "GET"}) + ) + is False + ) diff --git a/tests/utils.py b/tests/utils.py index b6aeeb185..7d6530b45 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,15 +1,9 @@ import base64 import os -import typing from time import sleep from uuid import uuid4 -try: - import pydantic.v1 as pydantic # type: ignore -except ImportError: - import pydantic # type: ignore - -from langfuse.api.client import FernLangfuse +from langfuse.api import LangfuseAPI def create_uuid(): @@ -19,69 +13,13 @@ def create_uuid(): def get_api(): sleep(2) - return FernLangfuse( + return LangfuseAPI( username=os.environ.get("LANGFUSE_PUBLIC_KEY"), password=os.environ.get("LANGFUSE_SECRET_KEY"), base_url=os.environ.get("LANGFUSE_BASE_URL"), ) -class LlmUsageWithCost(pydantic.BaseModel): - prompt_tokens: typing.Optional[int] = pydantic.Field( - alias="promptTokens", default=None - ) - completion_tokens: typing.Optional[int] = pydantic.Field( - alias="completionTokens", default=None - ) - total_tokens: typing.Optional[int] = pydantic.Field( - alias="totalTokens", default=None - ) - input_cost: typing.Optional[float] = pydantic.Field(alias="inputCost", default=None) - output_cost: typing.Optional[float] = pydantic.Field( - alias="outputCost", default=None - ) - total_cost: typing.Optional[float] = pydantic.Field(alias="totalCost", default=None) - - -class CompletionUsage(pydantic.BaseModel): - completion_tokens: int - """Number of tokens in the generated completion.""" - - prompt_tokens: int - """Number of tokens in the prompt.""" - - total_tokens: int - """Total number of tokens used in the request (prompt + completion).""" - - -class LlmUsage(pydantic.BaseModel): - prompt_tokens: typing.Optional[int] = pydantic.Field( - alias="promptTokens", default=None - ) - completion_tokens: typing.Optional[int] = pydantic.Field( - alias="completionTokens", default=None - ) - total_tokens: typing.Optional[int] = pydantic.Field( - alias="totalTokens", default=None - ) - - def json(self, **kwargs: typing.Any) -> str: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().json(**kwargs_with_defaults) - - def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: - kwargs_with_defaults: typing.Any = { - "by_alias": True, - "exclude_unset": True, - **kwargs, - } - return super().dict(**kwargs_with_defaults) - - def encode_file_to_base64(image_path) -> str: with open(image_path, "rb") as file: return base64.b64encode(file.read()).decode("utf-8") From 6d054560d641ae99f10f550c3ce4ade15ee0d615 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 24 Feb 2026 17:33:36 +0000 Subject: [PATCH 188/296] chore: release v4.0.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index d1e9207dd..9b67f697f 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.5" +__version__ = "4.0.0" diff --git a/pyproject.toml b/pyproject.toml index b632cdebc..4509f31c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.5" +version = "4.0.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From cea63757086ab1f9f87994f2a61074060f0f92e4 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:38:01 +0100 Subject: [PATCH 189/296] Revert "chore: release v4.0.0" This reverts commit 6d054560d641ae99f10f550c3ce4ade15ee0d615. --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 9b67f697f..d1e9207dd 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "4.0.0" +__version__ = "3.14.5" diff --git a/pyproject.toml b/pyproject.toml index 4509f31c2..b632cdebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "4.0.0" +version = "3.14.5" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 6a9507de259d718b0453a4c0772f8f794bd6995e Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:51:19 +0100 Subject: [PATCH 190/296] chore: fix release script for prereleases --- .github/workflows/release.yml | 61 +++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 551f6fad6..86ce3677b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,11 @@ on: - patch - minor - major - - prerelease + - prepatch + - preminor + - premajor prerelease_type: - description: 'Pre-release type (only used if version is "prerelease")' + description: 'Pre-release type (used when version is prepatch/preminor/premajor)' type: choice default: "" options: @@ -26,7 +28,7 @@ on: type: string default: "" confirm_major: - description: "Type RELEASE MAJOR to confirm a major release" + description: "Type RELEASE MAJOR to confirm a major or premajor release" required: false default: "" @@ -52,10 +54,10 @@ jobs: fi - name: Confirm major release - if: ${{ inputs.version == 'major' && inputs.dry_run == false }} + if: ${{ inputs.version == 'major' || inputs.version == 'premajor' }} run: | if [ "${{ inputs.confirm_major }}" != "RELEASE MAJOR" ]; then - echo "❌ For major releases, set confirm_major to RELEASE MAJOR" + echo "❌ For major/premajor releases, set confirm_major to RELEASE MAJOR" exit 1 fi @@ -108,12 +110,50 @@ jobs: # Parse version components IFS='.' read -r major minor patch <<< "$base_version" - if [ "$version_type" = "prerelease" ]; then + if echo "$current_version" | grep -qE '(a|b|rc)[0-9]+$'; then + current_is_prerelease="true" + else + current_is_prerelease="false" + fi + + if [ "$version_type" = "premajor" ] || [ "$version_type" = "preminor" ] || [ "$version_type" = "prepatch" ]; then if [ -z "$prerelease_type" ]; then - echo "❌ Error: prerelease_type must be specified when version is 'prerelease'" + echo "❌ Error: prerelease_type must be specified when version is a prerelease variant" exit 1 fi + # Determine the prerelease base version target + case "$version_type" in + premajor) + if [ "$current_is_prerelease" = "true" ] && [ "$minor" -eq 0 ] && [ "$patch" -eq 0 ]; then + target_major=$major + else + target_major=$((major + 1)) + fi + target_minor=0 + target_patch=0 + ;; + preminor) + target_major=$major + if [ "$current_is_prerelease" = "true" ] && [ "$patch" -eq 0 ] && [ "$minor" -gt 0 ]; then + target_minor=$minor + else + target_minor=$((minor + 1)) + fi + target_patch=0 + ;; + prepatch) + target_major=$major + target_minor=$minor + if [ "$current_is_prerelease" = "true" ] && [ "$patch" -gt 0 ]; then + target_patch=$patch + else + target_patch=$((patch + 1)) + fi + ;; + esac + target_base_version="${target_major}.${target_minor}.${target_patch}" + # Map prerelease type to Python suffix case "$prerelease_type" in alpha) suffix="a" ;; @@ -126,15 +166,16 @@ jobs: pre_num="$prerelease_increment" else # Check if current version is same type of prerelease, if so increment - if echo "$current_version" | grep -qE "${suffix}[0-9]+$"; then - current_pre_num=$(echo "$current_version" | sed -E "s/.*${suffix}([0-9]+)$/\1/") + escaped_target_base_version=$(echo "$target_base_version" | sed 's/\./\\./g') + if echo "$current_version" | grep -qE "^${escaped_target_base_version}${suffix}[0-9]+$"; then + current_pre_num=$(echo "$current_version" | sed -E "s/^${escaped_target_base_version}${suffix}([0-9]+)$/\1/") pre_num=$((current_pre_num + 1)) else pre_num=1 fi fi - new_version="${base_version}${suffix}${pre_num}" + new_version="${target_base_version}${suffix}${pre_num}" is_prerelease="true" else # Standard version bump From b31753ad885157c617a4320e53f21c4d72c851e7 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 24 Feb 2026 17:52:26 +0000 Subject: [PATCH 191/296] chore: release v4.0.0b1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index d1e9207dd..8d6183e0c 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.5" +__version__ = "4.0.0b1" diff --git a/pyproject.toml b/pyproject.toml index b632cdebc..8437cfe49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.5" +version = "4.0.0b1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 11d7ab6c3715506ed601ed3dd0d2f4a4c93deca4 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 24 Feb 2026 19:54:37 +0100 Subject: [PATCH 192/296] feat(api): update API spec from langfuse/langfuse 784ab09 (#1538) Co-authored-by: langfuse-bot --- langfuse/api/observations_v2/client.py | 10 ++++++---- langfuse/api/observations_v2/raw_client.py | 10 ++++++---- langfuse/api/score_v2/client.py | 10 ++++++++++ langfuse/api/score_v2/raw_client.py | 10 ++++++++++ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/langfuse/api/observations_v2/client.py b/langfuse/api/observations_v2/client.py index 570966958..923a4db4b 100644 --- a/langfuse/api/observations_v2/client.py +++ b/langfuse/api/observations_v2/client.py @@ -93,8 +93,9 @@ def get_many( Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. parse_io_as_json : typing.Optional[bool] - Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. - Defaults to `false` if not provided. + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] @@ -344,8 +345,9 @@ async def get_many( Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. parse_io_as_json : typing.Optional[bool] - Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. - Defaults to `false` if not provided. + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] diff --git a/langfuse/api/observations_v2/raw_client.py b/langfuse/api/observations_v2/raw_client.py index 66beee037..f65a45502 100644 --- a/langfuse/api/observations_v2/raw_client.py +++ b/langfuse/api/observations_v2/raw_client.py @@ -91,8 +91,9 @@ def get_many( Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. parse_io_as_json : typing.Optional[bool] - Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. - Defaults to `false` if not provided. + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] @@ -401,8 +402,9 @@ async def get_many( Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. parse_io_as_json : typing.Optional[bool] - Set to `true` to parse input/output fields as JSON, or `false` to return raw strings. - Defaults to `false` if not provided. + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] diff --git a/langfuse/api/score_v2/client.py b/langfuse/api/score_v2/client.py index c30afe18b..a58bec571 100644 --- a/langfuse/api/score_v2/client.py +++ b/langfuse/api/score_v2/client.py @@ -50,6 +50,7 @@ def get( data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> GetScoresResponse: """ @@ -117,6 +118,9 @@ def get( fields : typing.Optional[str] Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + filter : typing.Optional[str] + A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -159,6 +163,7 @@ def get( data_type=data_type, trace_tags=trace_tags, fields=fields, + filter=filter, request_options=request_options, ) return _response.data @@ -241,6 +246,7 @@ async def get( data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> GetScoresResponse: """ @@ -308,6 +314,9 @@ async def get( fields : typing.Optional[str] Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + filter : typing.Optional[str] + A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -358,6 +367,7 @@ async def main() -> None: data_type=data_type, trace_tags=trace_tags, fields=fields, + filter=filter, request_options=request_options, ) return _response.data diff --git a/langfuse/api/score_v2/raw_client.py b/langfuse/api/score_v2/raw_client.py index b47a6cd1d..b92150144 100644 --- a/langfuse/api/score_v2/raw_client.py +++ b/langfuse/api/score_v2/raw_client.py @@ -49,6 +49,7 @@ def get( data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[GetScoresResponse]: """ @@ -116,6 +117,9 @@ def get( fields : typing.Optional[str] Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + filter : typing.Optional[str] + A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -151,6 +155,7 @@ def get( "dataType": data_type, "traceTags": trace_tags, "fields": fields, + "filter": filter, }, request_options=request_options, ) @@ -361,6 +366,7 @@ async def get( data_type: typing.Optional[ScoreDataType] = None, trace_tags: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, fields: typing.Optional[str] = None, + filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[GetScoresResponse]: """ @@ -428,6 +434,9 @@ async def get( fields : typing.Optional[str] Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + filter : typing.Optional[str] + A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -463,6 +472,7 @@ async def get( "dataType": data_type, "traceTags": trace_tags, "fields": fields, + "filter": filter, }, request_options=request_options, ) From 7ff952236d15ff4fd6b7dccaddb64c95fbaf8ee6 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:47:32 +0100 Subject: [PATCH 193/296] fix(media): retry failed uploads (#1540) --- langfuse/_task_manager/media_manager.py | 35 +++++++++-- tests/test_media_manager.py | 80 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 tests/test_media_manager.py diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index 58b24e742..c885f9ba4 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -252,13 +252,36 @@ def _process_upload_media_job( headers["x-ms-blob-type"] = "BlockBlob" headers["x-amz-checksum-sha256"] = data["content_sha256_hash"] + def _upload_with_status_check() -> httpx.Response: + response = self._httpx_client.put( + upload_url, + headers=headers, + content=data["content_bytes"], + ) + response.raise_for_status() + + return response + upload_start_time = time.time() - upload_response = self._request_with_backoff( - self._httpx_client.put, - upload_url, - headers=headers, - content=data["content_bytes"], - ) + + try: + upload_response = self._request_with_backoff(_upload_with_status_check) + except httpx.HTTPStatusError as e: + upload_time_ms = int((time.time() - upload_start_time) * 1000) + failed_response = e.response + + if failed_response is not None: + self._request_with_backoff( + self._api_client.media.patch, + media_id=data["media_id"], + uploaded_at=_get_timestamp(), + upload_http_status=failed_response.status_code, + upload_http_error=failed_response.text, + upload_time_ms=upload_time_ms, + ) + + raise + upload_time_ms = int((time.time() - upload_start_time) * 1000) self._request_with_backoff( diff --git a/tests/test_media_manager.py b/tests/test_media_manager.py new file mode 100644 index 000000000..7a10da7dc --- /dev/null +++ b/tests/test_media_manager.py @@ -0,0 +1,80 @@ +from queue import Queue +from types import SimpleNamespace +from unittest.mock import Mock + +import httpx +import pytest + +from langfuse._task_manager.media_manager import MediaManager + + +def _upload_response(status_code: int, text: str = "") -> httpx.Response: + request = httpx.Request("PUT", "https://example.com/upload") + return httpx.Response(status_code=status_code, request=request, text=text) + + +def _upload_job() -> dict: + return { + "media_id": "media-id", + "content_bytes": b"payload", + "content_type": "image/jpeg", + "content_length": 7, + "content_sha256_hash": "sha256hash", + "trace_id": "trace-id", + "observation_id": None, + "field": "input", + } + + +def test_media_upload_retries_on_retryable_http_status(): + media_api = Mock() + media_api.get_upload_url.return_value = SimpleNamespace( + upload_url="https://example.com/upload", + media_id="media-id", + ) + media_api.patch.return_value = None + + httpx_client = Mock() + httpx_client.put.side_effect = [ + _upload_response(503, "temporary failure"), + _upload_response(200, "ok"), + ] + + manager = MediaManager( + api_client=SimpleNamespace(media=media_api), + httpx_client=httpx_client, + media_upload_queue=Queue(), + max_retries=3, + ) + + manager._process_upload_media_job(data=_upload_job()) + + assert httpx_client.put.call_count == 2 + media_api.patch.assert_called_once() + assert media_api.patch.call_args.kwargs["upload_http_status"] == 200 + + +def test_media_upload_gives_up_on_non_retryable_http_status(): + media_api = Mock() + media_api.get_upload_url.return_value = SimpleNamespace( + upload_url="https://example.com/upload", + media_id="media-id", + ) + media_api.patch.return_value = None + + httpx_client = Mock() + httpx_client.put.return_value = _upload_response(403, "forbidden") + + manager = MediaManager( + api_client=SimpleNamespace(media=media_api), + httpx_client=httpx_client, + media_upload_queue=Queue(), + max_retries=3, + ) + + with pytest.raises(httpx.HTTPStatusError): + manager._process_upload_media_job(data=_upload_job()) + + assert httpx_client.put.call_count == 1 + media_api.patch.assert_called_once() + assert media_api.patch.call_args.kwargs["upload_http_status"] == 403 From 30b46ba706cf662cf4adf4d8af8e578c093e405a Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 26 Feb 2026 10:58:40 +0100 Subject: [PATCH 194/296] feat(api): update API spec from langfuse/langfuse 25b5139 (#1543) Co-authored-by: langfuse-bot --- langfuse/api/score_v2/client.py | 4 ++-- langfuse/api/score_v2/raw_client.py | 4 ++-- .../api/score_v2/types/get_scores_response_trace_data.py | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/langfuse/api/score_v2/client.py b/langfuse/api/score_v2/client.py index a58bec571..4ee5cf372 100644 --- a/langfuse/api/score_v2/client.py +++ b/langfuse/api/score_v2/client.py @@ -116,7 +116,7 @@ def get( Only scores linked to traces that include all of these tags will be returned. fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment, sessionId). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. filter : typing.Optional[str] A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. @@ -312,7 +312,7 @@ async def get( Only scores linked to traces that include all of these tags will be returned. fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment, sessionId). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. filter : typing.Optional[str] A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. diff --git a/langfuse/api/score_v2/raw_client.py b/langfuse/api/score_v2/raw_client.py index b92150144..2062b5bd9 100644 --- a/langfuse/api/score_v2/raw_client.py +++ b/langfuse/api/score_v2/raw_client.py @@ -115,7 +115,7 @@ def get( Only scores linked to traces that include all of these tags will be returned. fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment, sessionId). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. filter : typing.Optional[str] A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. @@ -432,7 +432,7 @@ async def get( Only scores linked to traces that include all of these tags will be returned. fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. + Comma-separated list of field groups to include in the response. Available field groups: 'score' (core score fields), 'trace' (trace properties: userId, tags, environment, sessionId). If not specified, both 'score' and 'trace' are returned by default. Example: 'score' to exclude trace data, 'score,trace' to include both. Note: When filtering by trace properties (using userId or traceTags parameters), the 'trace' field group must be included, otherwise a 400 error will be returned. filter : typing.Optional[str] A JSON stringified array of filter objects. Each object requires type, column, operator, and value. Supports filtering by score metadata using the stringObject type. Example: [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. Supported types: stringObject (metadata key-value filtering), string, number, datetime, stringOptions, arrayOptions. Supported operators for stringObject: =, contains, does not contain, starts with, ends with. diff --git a/langfuse/api/score_v2/types/get_scores_response_trace_data.py b/langfuse/api/score_v2/types/get_scores_response_trace_data.py index 674057172..306aaaf78 100644 --- a/langfuse/api/score_v2/types/get_scores_response_trace_data.py +++ b/langfuse/api/score_v2/types/get_scores_response_trace_data.py @@ -26,6 +26,13 @@ class GetScoresResponseTraceData(UniversalBaseModel): The environment of the trace referenced by score """ + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = pydantic.Field(default=None) + """ + The session ID associated with the trace referenced by score + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) From bb139f283abfa8bf7f9dc0a983a95a85f9d04051 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:38:05 +0100 Subject: [PATCH 195/296] chore(deps-dev): bump langgraph-checkpoint from 3.0.0 to 4.0.0 (#1541) Bumps [langgraph-checkpoint](https://github.com/langchain-ai/langgraph) from 3.0.0 to 4.0.0. - [Release notes](https://github.com/langchain-ai/langgraph/releases) - [Commits](https://github.com/langchain-ai/langgraph/compare/checkpoint==3.0.0...checkpoint==4.0.0) --- updated-dependencies: - dependency-name: langgraph-checkpoint dependency-version: 4.0.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 268 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 178 insertions(+), 90 deletions(-) diff --git a/poetry.lock b/poetry.lock index c0b23faf5..ce10280e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -37,6 +39,7 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -48,6 +51,7 @@ version = "0.0.130" description = "Universal library for evaluating AI models" optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, @@ -71,6 +75,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -82,6 +87,8 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -93,6 +100,7 @@ version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, @@ -104,6 +112,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -115,6 +124,7 @@ version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, @@ -237,6 +247,7 @@ version = "0.14.0" description = "Mustache templating language renderer" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, @@ -248,10 +259,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "distlib" @@ -259,6 +272,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -270,6 +284,7 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -281,6 +296,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -298,6 +315,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -312,6 +330,7 @@ version = "3.20.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, @@ -323,6 +342,7 @@ version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -340,6 +360,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -351,6 +372,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -372,6 +394,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -384,7 +407,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -396,6 +419,7 @@ version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, @@ -410,6 +434,7 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -424,6 +449,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -433,12 +459,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -447,6 +473,7 @@ version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -458,6 +485,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -475,6 +503,7 @@ version = "0.11.1" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, @@ -586,6 +615,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -600,6 +630,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -611,6 +642,7 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -632,6 +664,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -646,6 +679,7 @@ version = "1.0.1" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" +groups = ["dev"] files = [ {file = "langchain-1.0.1-py3-none-any.whl", hash = "sha256:48fd616413fee4843f12cad49e8b74ad6fc159640142ca885d03bd925cd24503"}, {file = "langchain-1.0.1.tar.gz", hash = "sha256:8bf60e096ca9c06626d9161a46651405ef1d12b5863f7679283666f83f760dc5"}, @@ -679,6 +713,7 @@ version = "1.2.11" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" +groups = ["dev"] files = [ {file = "langchain_core-1.2.11-py3-none-any.whl", hash = "sha256:ae11ceb8dda60d0b9d09e763116e592f1683327c17be5b715f350fd29aee65d3"}, {file = "langchain_core-1.2.11.tar.gz", hash = "sha256:f164bb36602dd74a3a50c1334fca75309ad5ed95767acdfdbb9fa95ce28a1e01"}, @@ -700,6 +735,7 @@ version = "0.3.34" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0.0,>=3.9.0" +groups = ["dev"] files = [ {file = "langchain_openai-0.3.34-py3-none-any.whl", hash = "sha256:08d61d68a6d869c70d542171e149b9065668dedfc4fafcd4de8aeb5b933030a9"}, {file = "langchain_openai-0.3.34.tar.gz", hash = "sha256:57916d462be5b8fd19e5cb2f00d4e5cf0465266a292d583de2fc693a55ba190e"}, @@ -712,62 +748,66 @@ tiktoken = ">=0.7.0,<1.0.0" [[package]] name = "langgraph" -version = "1.0.2" +version = "1.0.9" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph-1.0.2-py3-none-any.whl", hash = "sha256:b3d56b8c01de857b5fb1da107e8eab6e30512a377685eeedb4f76456724c9729"}, - {file = "langgraph-1.0.2.tar.gz", hash = "sha256:dae1af08d6025cb1fcaed68f502c01af7d634d9044787c853a46c791cfc52f67"}, + {file = "langgraph-1.0.9-py3-none-any.whl", hash = "sha256:bce0d1f3e9a20434215a2a818395a58aedfc11c87bd6b52706c0db5c05ec44ec"}, + {file = "langgraph-1.0.9.tar.gz", hash = "sha256:feac2729faba7d3c325bef76f240d7d7f66b02d2cbf4fdb1ed7d0cc83f963651"}, ] [package.dependencies] langchain-core = ">=0.1" -langgraph-checkpoint = ">=2.1.0,<4.0.0" -langgraph-prebuilt = ">=1.0.2,<1.1.0" -langgraph-sdk = ">=0.2.2,<0.3.0" +langgraph-checkpoint = ">=2.1.0,<5.0.0" +langgraph-prebuilt = ">=1.0.8,<1.1.0" +langgraph-sdk = ">=0.3.0,<0.4.0" pydantic = ">=2.7.4" xxhash = ">=3.5.0" [[package]] name = "langgraph-checkpoint" -version = "3.0.0" +version = "4.0.0" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph_checkpoint-3.0.0-py3-none-any.whl", hash = "sha256:560beb83e629784ab689212a3d60834fb3196b4bbe1d6ac18e5cad5d85d46010"}, - {file = "langgraph_checkpoint-3.0.0.tar.gz", hash = "sha256:f738695ad938878d8f4775d907d9629e9fcd345b1950196effb08f088c52369e"}, + {file = "langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784"}, + {file = "langgraph_checkpoint-4.0.0.tar.gz", hash = "sha256:814d1bd050fac029476558d8e68d87bce9009a0262d04a2c14b918255954a624"}, ] [package.dependencies] langchain-core = ">=0.2.38" -ormsgpack = ">=1.10.0" +ormsgpack = ">=1.12.0" [[package]] name = "langgraph-prebuilt" -version = "1.0.2" +version = "1.0.8" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph_prebuilt-1.0.2-py3-none-any.whl", hash = "sha256:d9499f7c449fb637ee7b87e3f6a3b74095f4202053c74d33894bd839ea4c57c7"}, - {file = "langgraph_prebuilt-1.0.2.tar.gz", hash = "sha256:9896dbabf04f086eb59df4294f54ab5bdb21cd78e27e0a10e695dffd1cc6097d"}, + {file = "langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0"}, + {file = "langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69"}, ] [package.dependencies] langchain-core = ">=1.0.0" -langgraph-checkpoint = ">=2.1.0,<4.0.0" +langgraph-checkpoint = ">=2.1.0,<5.0.0" [[package]] name = "langgraph-sdk" -version = "0.2.9" +version = "0.3.6" description = "SDK for interacting with LangGraph API" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "langgraph_sdk-0.2.9-py3-none-any.whl", hash = "sha256:fbf302edadbf0fb343596f91c597794e936ef68eebc0d3e1d358b6f9f72a1429"}, - {file = "langgraph_sdk-0.2.9.tar.gz", hash = "sha256:b3bd04c6be4fa382996cd2be8fbc1e7cc94857d2bc6b6f4599a7f2a245975303"}, + {file = "langgraph_sdk-0.3.6-py3-none-any.whl", hash = "sha256:7df2fd552ad7262d0baf8e1f849dce1d62186e76dcdd36db9dc5bdfa5c3fc20f"}, + {file = "langgraph_sdk-0.3.6.tar.gz", hash = "sha256:7650f607f89c1586db5bee391b1a8754cbe1fc83b721ff2f1450f8906e790bd7"}, ] [package.dependencies] @@ -780,6 +820,7 @@ version = "0.6.3" description = "Client library to connect to the LangSmith Observability and Evaluation Platform." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "langsmith-0.6.3-py3-none-any.whl", hash = "sha256:44fdf8084165513e6bede9dda715e7b460b1b3f57ac69f2ca3f03afa911233ec"}, {file = "langsmith-0.6.3.tar.gz", hash = "sha256:33246769c0bb24e2c17e0c34bb21931084437613cd37faf83bd0978a297b826f"}, @@ -796,7 +837,7 @@ uuid-utils = ">=0.12.0,<1.0" zstandard = ">=0.23.0" [package.extras] -claude-agent-sdk = ["claude-agent-sdk (>=0.1.0)"] +claude-agent-sdk = ["claude-agent-sdk (>=0.1.0) ; python_version >= \"3.10\""] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] openai-agents = ["openai-agents (>=0.0.3)"] otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] @@ -809,6 +850,7 @@ version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -907,6 +949,7 @@ version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, @@ -967,6 +1010,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -978,6 +1022,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -989,6 +1034,7 @@ version = "2.5.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "openai-2.5.0-py3-none-any.whl", hash = "sha256:21380e5f52a71666dbadbf322dd518bdf2b9d11ed0bb3f96bea17310302d6280"}, {file = "openai-2.5.0.tar.gz", hash = "sha256:f8fa7611f96886a0f31ac6b97e58bc0ada494b255ee2cfd51c8eb502cfcb4814"}, @@ -1016,6 +1062,7 @@ version = "1.38.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582"}, {file = "opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12"}, @@ -1031,6 +1078,7 @@ version = "1.38.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a"}, {file = "opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c"}, @@ -1045,6 +1093,7 @@ version = "1.38.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b"}, {file = "opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b"}, @@ -1065,6 +1114,7 @@ version = "0.59b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee"}, {file = "opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc"}, @@ -1082,6 +1132,7 @@ version = "0.59b0" description = "Thread context propagation support for OpenTelemetry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca"}, {file = "opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3"}, @@ -1098,6 +1149,7 @@ version = "1.38.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18"}, {file = "opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468"}, @@ -1112,6 +1164,7 @@ version = "1.38.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b"}, {file = "opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe"}, @@ -1128,6 +1181,7 @@ version = "0.59b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed"}, {file = "opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0"}, @@ -1143,6 +1197,7 @@ version = "3.11.3" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, @@ -1231,67 +1286,61 @@ files = [ [[package]] name = "ormsgpack" -version = "1.11.0" -description = "" +version = "1.12.2" +description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.9" -files = [ - {file = "ormsgpack-1.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:03d4e658dd6e1882a552ce1d13cc7b49157414e7d56a4091fbe7823225b08cba"}, - {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb67eb913c2b703f0ed39607fc56e50724dd41f92ce080a586b4d6149eb3fe4"}, - {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e54175b92411f73a238e5653a998627f6660de3def37d9dd7213e0fd264ca56"}, - {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca2b197f4556e1823d1319869d4c5dc278be335286d2308b0ed88b59a5afcc25"}, - {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc62388262f58c792fe1e450e1d9dbcc174ed2fb0b43db1675dd7c5ff2319d6a"}, - {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c48bc10af74adfbc9113f3fb160dc07c61ad9239ef264c17e449eba3de343dc2"}, - {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a608d3a1d4fa4acdc5082168a54513cff91f47764cef435e81a483452f5f7647"}, - {file = "ormsgpack-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:97217b4f7f599ba45916b9c4c4b1d5656e8e2a4d91e2e191d72a7569d3c30923"}, - {file = "ormsgpack-1.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c7be823f47d8e36648d4bc90634b93f02b7d7cc7480081195f34767e86f181fb"}, - {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68accf15d1b013812755c0eb7a30e1fc2f81eb603a1a143bf0cda1b301cfa797"}, - {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:805d06fb277d9a4e503c0c707545b49cde66cbb2f84e5cf7c58d81dfc20d8658"}, - {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1e57cdf003e77acc43643bda151dc01f97147a64b11cdee1380bb9698a7601c"}, - {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37fc05bdaabd994097c62e2f3e08f66b03f856a640ede6dc5ea340bd15b77f4d"}, - {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a6e9db6c73eb46b2e4d97bdffd1368a66f54e6806b563a997b19c004ef165e1d"}, - {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9c44eae5ac0196ffc8b5ed497c75511056508f2303fa4d36b208eb820cf209e"}, - {file = "ormsgpack-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:11d0dfaf40ae7c6de4f7dbd1e4892e2e6a55d911ab1774357c481158d17371e4"}, - {file = "ormsgpack-1.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:0c63a3f7199a3099c90398a1bdf0cb577b06651a442dc5efe67f2882665e5b02"}, - {file = "ormsgpack-1.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3434d0c8d67de27d9010222de07fb6810fb9af3bb7372354ffa19257ac0eb83b"}, - {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2da5bd097e8dbfa4eb0d4ccfe79acd6f538dee4493579e2debfe4fc8f4ca89b"}, - {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fdbaa0a5a8606a486960b60c24f2d5235d30ac7a8b98eeaea9854bffef14dc3d"}, - {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3682f24f800c1837017ee90ce321086b2cbaef88db7d4cdbbda1582aa6508159"}, - {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcca21202bb05ccbf3e0e92f560ee59b9331182e4c09c965a28155efbb134993"}, - {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c30e5c4655ba46152d722ec7468e8302195e6db362ec1ae2c206bc64f6030e43"}, - {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7138a341f9e2c08c59368f03d3be25e8b87b3baaf10d30fb1f6f6b52f3d47944"}, - {file = "ormsgpack-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4bd8589b78a11026d47f4edf13c1ceab9088bb12451f34396afe6497db28a27"}, - {file = "ormsgpack-1.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:e5e746a1223e70f111d4001dab9585ac8639eee8979ca0c8db37f646bf2961da"}, - {file = "ormsgpack-1.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e7b36ab7b45cb95217ae1f05f1318b14a3e5ef73cb00804c0f06233f81a14e8"}, - {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43402d67e03a9a35cc147c8c03f0c377cad016624479e1ee5b879b8425551484"}, - {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64fd992f932764d6306b70ddc755c1bc3405c4c6a69f77a36acf7af1c8f5ada4"}, - {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0362fb7fe4a29c046c8ea799303079a09372653a1ce5a5a588f3bbb8088368d0"}, - {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:de2f7a65a9d178ed57be49eba3d0fc9b833c32beaa19dbd4ba56014d3c20b152"}, - {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f38cfae95461466055af966fc922d06db4e1654966385cda2828653096db34da"}, - {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c88396189d238f183cea7831b07a305ab5c90d6d29b53288ae11200bd956357b"}, - {file = "ormsgpack-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5403d1a945dd7c81044cebeca3f00a28a0f4248b33242a5d2d82111628043725"}, - {file = "ormsgpack-1.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c57357b8d43b49722b876edf317bdad9e6d52071b523fdd7394c30cd1c67d5a0"}, - {file = "ormsgpack-1.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d390907d90fd0c908211592c485054d7a80990697ef4dff4e436ac18e1aab98a"}, - {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6153c2e92e789509098e04c9aa116b16673bd88ec78fbe0031deeb34ab642d10"}, - {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2b2c2a065a94d742212b2018e1fecd8f8d72f3c50b53a97d1f407418093446d"}, - {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110e65b5340f3d7ef8b0009deae3c6b169437e6b43ad5a57fd1748085d29d2ac"}, - {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c27e186fca96ab34662723e65b420919910acbbc50fc8e1a44e08f26268cb0e0"}, - {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d56b1f877c13d499052d37a3db2378a97d5e1588d264f5040b3412aee23d742c"}, - {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c88e28cd567c0a3269f624b4ade28142d5e502c8e826115093c572007af5be0a"}, - {file = "ormsgpack-1.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:8811160573dc0a65f62f7e0792c4ca6b7108dfa50771edb93f9b84e2d45a08ae"}, - {file = "ormsgpack-1.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:23e30a8d3c17484cf74e75e6134322255bd08bc2b5b295cc9c442f4bae5f3c2d"}, - {file = "ormsgpack-1.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2905816502adfaf8386a01dd85f936cd378d243f4f5ee2ff46f67f6298dc90d5"}, - {file = "ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04402fb9a0a9b9f18fbafd6d5f8398ee99b3ec619fb63952d3a954bc9d47daa"}, - {file = "ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a025ec07ac52056ecfd9e57b5cbc6fff163f62cb9805012b56cda599157f8ef2"}, - {file = "ormsgpack-1.11.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:354c6a5039faf63b63d8f42ec7915583a4a56e10b319284370a5a89c4382d985"}, - {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7058c85cc13dd329bc7b528e38626c6babcd0066d6e9163330a1509fe0aa4707"}, - {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e15b634be324fb18dab7aa82ab929a0d57d42c12650ae3dedd07d8d31b17733"}, - {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6329e6eae9dfe600962739a6e060ea82885ec58b8338875c5ac35080da970f94"}, - {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b27546c28f92b9eb757620f7f1ed89fb7b07be3b9f4ba1b7de75761ec1c4bcc8"}, - {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:26a17919d9144b4ac7112dbbadef07927abbe436be2cf99a703a19afe7dd5c8b"}, - {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5352868ee4cdc00656bf216b56bc654f72ac3008eb36e12561f6337bb7104b45"}, - {file = "ormsgpack-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:2ffe36f1f441a40949e8587f5aa3d3fc9f100576925aab667117403eab494338"}, - {file = "ormsgpack-1.11.0.tar.gz", hash = "sha256:7c9988e78fedba3292541eb3bb274fa63044ef4da2ddb47259ea70c05dee4206"}, +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657"}, + {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163"}, + {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59f5da97000c12bc2d50e988bdc8576b21f6ab4e608489879d35b2c07a8ab51a"}, + {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e4459c3f27066beadb2b81ea48a076a417aafffff7df1d3c11c519190ed44f2"}, + {file = "ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a1c460655d7288407ffa09065e322a7231997c0d62ce914bf3a96ad2dc6dedd"}, + {file = "ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:458e4568be13d311ef7d8877275e7ccbe06c0e01b39baaac874caaa0f46d826c"}, + {file = "ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cde5eaa6c6cbc8622db71e4a23de56828e3d876aeb6460ffbcb5b8aff91093b"}, + {file = "ormsgpack-1.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc7a33be14c347893edbb1ceda89afbf14c467d593a5ee92c11de4f1666b4d4f"}, + {file = "ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9"}, + {file = "ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a"}, + {file = "ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5"}, + {file = "ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181"}, + {file = "ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b"}, + {file = "ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92"}, + {file = "ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a"}, + {file = "ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c"}, + {file = "ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd"}, + {file = "ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7"}, + {file = "ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d"}, + {file = "ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e"}, + {file = "ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc"}, + {file = "ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e"}, + {file = "ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6"}, + {file = "ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd"}, + {file = "ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4"}, + {file = "ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6"}, + {file = "ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355"}, + {file = "ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1"}, + {file = "ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172"}, + {file = "ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d"}, + {file = "ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7"}, + {file = "ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685"}, + {file = "ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258"}, + {file = "ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9"}, + {file = "ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709"}, + {file = "ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c"}, + {file = "ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553"}, + {file = "ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13"}, + {file = "ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d"}, + {file = "ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede"}, + {file = "ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e"}, + {file = "ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285"}, + {file = "ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f"}, + {file = "ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c"}, + {file = "ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8"}, + {file = "ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033"}, + {file = "ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d"}, + {file = "ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2"}, + {file = "ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33"}, ] [[package]] @@ -1300,6 +1349,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1311,6 +1361,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1322,6 +1373,7 @@ version = "15.0.4" description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, @@ -1338,6 +1390,7 @@ version = "4.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, @@ -1354,6 +1407,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1369,6 +1423,7 @@ version = "0.9.0" description = "A fast C-implemented library for Levenshtein distance" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, @@ -1433,6 +1488,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1451,6 +1507,7 @@ version = "6.33.5" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"}, {file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"}, @@ -1470,6 +1527,7 @@ version = "2.12.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, @@ -1483,7 +1541,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1491,6 +1549,7 @@ version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, @@ -1620,6 +1679,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1634,6 +1694,7 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1657,6 +1718,7 @@ version = "1.1.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_asyncio-1.1.1-py3-none-any.whl", hash = "sha256:726339d30fcfde24691f589445b9b67d058b311ac632b1d704e97f20f1d878da"}, {file = "pytest_asyncio-1.1.1.tar.gz", hash = "sha256:b72d215c38e2c91dbb32f275e0b5be69602d7869910e109360e375129960a649"}, @@ -1676,6 +1738,7 @@ version = "1.1.3" description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, @@ -1690,6 +1753,7 @@ version = "2.4.0" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, @@ -1704,6 +1768,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1724,6 +1789,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -1806,6 +1872,7 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -1822,6 +1889,7 @@ version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, @@ -1946,6 +2014,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1967,6 +2036,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -1981,6 +2051,7 @@ version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, @@ -2145,6 +2216,7 @@ version = "0.15.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d"}, {file = "ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e"}, @@ -2172,6 +2244,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2183,6 +2256,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -2198,6 +2272,7 @@ version = "0.12.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, @@ -2271,6 +2346,8 @@ version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -2322,6 +2399,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2343,6 +2421,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -2354,6 +2433,7 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -2368,16 +2448,17 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "uuid-utils" @@ -2385,6 +2466,7 @@ version = "0.12.0" description = "Drop-in replacement for Python UUID with bindings in Rust" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514"}, {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65"}, @@ -2416,6 +2498,7 @@ version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, @@ -2429,7 +2512,7 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "werkzeug" @@ -2437,6 +2520,7 @@ version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, @@ -2454,6 +2538,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -2544,6 +2629,7 @@ version = "3.6.0" description = "Python binding for xxHash" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, @@ -2693,13 +2779,14 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -2712,6 +2799,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -2815,9 +2903,9 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0)", "cffi (>=2.0.0b)"] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.10,<4.0" content-hash = "1153d68c8977db835b7425287bacb02a827f6d1a4a259ae6bacf010d2b91bc94" From 72aa1565a75766cdd0d1bc4962f34240b327bdc1 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 27 Feb 2026 15:32:16 +0100 Subject: [PATCH 196/296] feat(api): update API spec from langfuse/langfuse a93f65a (#1545) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 3 + langfuse/api/commons/__init__.py | 3 + langfuse/api/commons/types/__init__.py | 3 + langfuse/api/commons/types/observation_v2.py | 235 ++++++++++++++++++ .../types/observations_v2response.py | 3 +- 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 langfuse/api/commons/types/observation_v2.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index eddf81af0..8b70c0807 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -97,6 +97,7 @@ NumericScoreV1, Observation, ObservationLevel, + ObservationV2, ObservationsView, PricingTier, PricingTierCondition, @@ -423,6 +424,7 @@ "ObservationBody": ".ingestion", "ObservationLevel": ".commons", "ObservationType": ".ingestion", + "ObservationV2": ".commons", "Observations": ".observations", "ObservationsV2Meta": ".observations_v2", "ObservationsV2Response": ".observations_v2", @@ -714,6 +716,7 @@ def __dir__(): "ObservationBody", "ObservationLevel", "ObservationType", + "ObservationV2", "Observations", "ObservationsV2Meta", "ObservationsV2Response", diff --git a/langfuse/api/commons/__init__.py b/langfuse/api/commons/__init__.py index 43e4649d2..af79e3e25 100644 --- a/langfuse/api/commons/__init__.py +++ b/langfuse/api/commons/__init__.py @@ -32,6 +32,7 @@ NumericScoreV1, Observation, ObservationLevel, + ObservationV2, ObservationsView, PricingTier, PricingTierCondition, @@ -94,6 +95,7 @@ "NumericScoreV1": ".types", "Observation": ".types", "ObservationLevel": ".types", + "ObservationV2": ".types", "ObservationsView": ".types", "PricingTier": ".types", "PricingTierCondition": ".types", @@ -179,6 +181,7 @@ def __dir__(): "NumericScoreV1", "Observation", "ObservationLevel", + "ObservationV2", "ObservationsView", "PricingTier", "PricingTierCondition", diff --git a/langfuse/api/commons/types/__init__.py b/langfuse/api/commons/types/__init__.py index 97fc36d1d..b7834caf3 100644 --- a/langfuse/api/commons/types/__init__.py +++ b/langfuse/api/commons/types/__init__.py @@ -31,6 +31,7 @@ from .numeric_score_v1 import NumericScoreV1 from .observation import Observation from .observation_level import ObservationLevel + from .observation_v2 import ObservationV2 from .observations_view import ObservationsView from .pricing_tier import PricingTier from .pricing_tier_condition import PricingTierCondition @@ -80,6 +81,7 @@ "NumericScoreV1": ".numeric_score_v1", "Observation": ".observation", "ObservationLevel": ".observation_level", + "ObservationV2": ".observation_v2", "ObservationsView": ".observations_view", "PricingTier": ".pricing_tier", "PricingTierCondition": ".pricing_tier_condition", @@ -160,6 +162,7 @@ def __dir__(): "NumericScoreV1", "Observation", "ObservationLevel", + "ObservationV2", "ObservationsView", "PricingTier", "PricingTierCondition", diff --git a/langfuse/api/commons/types/observation_v2.py b/langfuse/api/commons/types/observation_v2.py new file mode 100644 index 000000000..149dfb422 --- /dev/null +++ b/langfuse/api/commons/types/observation_v2.py @@ -0,0 +1,235 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .observation_level import ObservationLevel + + +class ObservationV2(UniversalBaseModel): + """ + An observation from the v2 API with field-group-based selection. + Core fields are always present. Other fields are included only when their field group is requested. + """ + + id: str = pydantic.Field() + """ + The unique identifier of the observation + """ + + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = pydantic.Field(default=None) + """ + The trace ID associated with the observation + """ + + start_time: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="startTime") + ] = pydantic.Field() + """ + The start time of the observation + """ + + end_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="endTime") + ] = pydantic.Field(default=None) + """ + The end time of the observation + """ + + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] = ( + pydantic.Field() + ) + """ + The project ID this observation belongs to + """ + + parent_observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="parentObservationId") + ] = pydantic.Field(default=None) + """ + The parent observation ID + """ + + type: str = pydantic.Field() + """ + The type of the observation (e.g. GENERATION, SPAN, EVENT) + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + The name of the observation + """ + + level: typing.Optional[ObservationLevel] = pydantic.Field(default=None) + """ + The level of the observation + """ + + status_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="statusMessage") + ] = pydantic.Field(default=None) + """ + The status message of the observation + """ + + version: typing.Optional[str] = pydantic.Field(default=None) + """ + The version of the observation + """ + + environment: typing.Optional[str] = pydantic.Field(default=None) + """ + The environment from which this observation originated + """ + + bookmarked: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether the observation is bookmarked + """ + + public: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether the observation is public + """ + + user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="userId") + ] = pydantic.Field(default=None) + """ + The user ID associated with the observation + """ + + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = pydantic.Field(default=None) + """ + The session ID associated with the observation + """ + + completion_start_time: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completionStartTime") + ] = pydantic.Field(default=None) + """ + The completion start time of the observation + """ + + created_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="createdAt") + ] = pydantic.Field(default=None) + """ + The creation timestamp of the observation + """ + + updated_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="updatedAt") + ] = pydantic.Field(default=None) + """ + The last update timestamp of the observation + """ + + input: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + The input data of the observation + """ + + output: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + The output data of the observation + """ + + metadata: typing.Optional[typing.Any] = pydantic.Field(default=None) + """ + Additional metadata of the observation + """ + + provided_model_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="providedModelName") + ] = pydantic.Field(default=None) + """ + The model name as provided by the user + """ + + internal_model_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="internalModelId") + ] = pydantic.Field(default=None) + """ + The internal model ID matched by Langfuse + """ + + model_parameters: typing_extensions.Annotated[ + typing.Optional[typing.Any], FieldMetadata(alias="modelParameters") + ] = pydantic.Field(default=None) + """ + The parameters of the model used for the observation + """ + + usage_details: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, int]], FieldMetadata(alias="usageDetails") + ] = pydantic.Field(default=None) + """ + The usage details of the observation. Key is the usage metric name, value is the number of units consumed. + """ + + cost_details: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, float]], FieldMetadata(alias="costDetails") + ] = pydantic.Field(default=None) + """ + The cost details of the observation. Key is the cost metric name, value is the cost in USD. + """ + + total_cost: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="totalCost") + ] = pydantic.Field(default=None) + """ + The total cost of the observation in USD + """ + + prompt_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="promptId") + ] = pydantic.Field(default=None) + """ + The prompt ID associated with the observation + """ + + prompt_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="promptName") + ] = pydantic.Field(default=None) + """ + The prompt name associated with the observation + """ + + prompt_version: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="promptVersion") + ] = pydantic.Field(default=None) + """ + The prompt version associated with the observation + """ + + latency: typing.Optional[float] = pydantic.Field(default=None) + """ + The latency in seconds + """ + + time_to_first_token: typing_extensions.Annotated[ + typing.Optional[float], FieldMetadata(alias="timeToFirstToken") + ] = pydantic.Field(default=None) + """ + The time to first token in seconds + """ + + model_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="modelId") + ] = pydantic.Field(default=None) + """ + The matched model ID + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/observations_v2/types/observations_v2response.py b/langfuse/api/observations_v2/types/observations_v2response.py index e833e1918..0ee1fb8bd 100644 --- a/langfuse/api/observations_v2/types/observations_v2response.py +++ b/langfuse/api/observations_v2/types/observations_v2response.py @@ -3,6 +3,7 @@ import typing import pydantic +from ...commons.types.observation_v2 import ObservationV2 from ...core.pydantic_utilities import UniversalBaseModel from .observations_v2meta import ObservationsV2Meta @@ -15,7 +16,7 @@ class ObservationsV2Response(UniversalBaseModel): Use the `cursor` in `meta` to retrieve the next page of results. """ - data: typing.List[typing.Dict[str, typing.Any]] = pydantic.Field() + data: typing.List[ObservationV2] = pydantic.Field() """ Array of observation objects. Fields included depend on the `fields` parameter in the request. """ From c45f6ffef528600b3bc2debd3cacabb4053c681e Mon Sep 17 00:00:00 2001 From: Br1an <932039080@qq.com> Date: Mon, 2 Mar 2026 17:38:13 +0800 Subject: [PATCH 197/296] fix(langchain): skip priority-tier keys when subtracting token detail counts (#1549) --- langfuse/langchain/CallbackHandler.py | 8 +++++++ tests/test_parse_usage_model.py | 34 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/test_parse_usage_model.py diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index a1ed9b28d..7a907d5e7 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1189,6 +1189,10 @@ def _parse_usage_model(usage: Union[pydantic.BaseModel, dict]) -> Any: for key, value in input_token_details.items(): usage_model[f"input_{key}"] = value + # Skip priority-tier keys as they are not exclusive sub-categories + if key == "priority" or key.startswith("priority_"): + continue + if "input" in usage_model: usage_model["input"] = max(0, usage_model["input"] - value) @@ -1198,6 +1202,10 @@ def _parse_usage_model(usage: Union[pydantic.BaseModel, dict]) -> Any: for key, value in output_token_details.items(): usage_model[f"output_{key}"] = value + # Skip priority-tier keys as they are not exclusive sub-categories + if key == "priority" or key.startswith("priority_"): + continue + if "output" in usage_model: usage_model["output"] = max(0, usage_model["output"] - value) diff --git a/tests/test_parse_usage_model.py b/tests/test_parse_usage_model.py new file mode 100644 index 000000000..df441523c --- /dev/null +++ b/tests/test_parse_usage_model.py @@ -0,0 +1,34 @@ +from langfuse.langchain.CallbackHandler import _parse_usage_model + + +def test_standard_tier_input_token_details(): + """Standard tier: audio and cache_read are subtracted from input.""" + usage = { + "input_tokens": 13, + "output_tokens": 1, + "total_tokens": 14, + "input_token_details": {"audio": 0, "cache_read": 3}, + "output_token_details": {"audio": 0}, + } + result = _parse_usage_model(usage) + assert result["input"] == 10 # 13 - 0 (audio) - 3 (cache_read) + assert result["output"] == 1 # 1 - 0 (audio) + assert result["total"] == 14 + + +def test_priority_tier_not_subtracted(): + """Priority tier: 'priority' and 'priority_*' keys must NOT be subtracted.""" + usage = { + "input_tokens": 13, + "output_tokens": 1, + "total_tokens": 14, + "input_token_details": {"audio": 0, "priority_cache_read": 0, "priority": 13}, + "output_token_details": {"audio": 0, "priority_reasoning": 0, "priority": 1}, + } + result = _parse_usage_model(usage) + assert result["input"] == 13 # priority keys not subtracted + assert result["output"] == 1 + assert result["total"] == 14 + # Priority keys are still stored with prefixed names + assert result["input_priority"] == 13 + assert result["output_priority"] == 1 From 3e0fa96bc1e00334dadbed637f77f2e8384238ea Mon Sep 17 00:00:00 2001 From: Br1an <932039080@qq.com> Date: Mon, 2 Mar 2026 17:44:09 +0800 Subject: [PATCH 198/296] fix(experiment): replace escaped newlines with actual newlines in format output (#1547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ExperimentResult.format() method and related logging calls used escaped newlines (\\n → literal \n in output) instead of actual newline characters, causing the formatted output to display \n literally rather than producing line breaks. --- langfuse/experiment.py | 52 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/langfuse/experiment.py b/langfuse/experiment.py index 79a3bcdad..ee9f9eaa7 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -458,7 +458,7 @@ def format(self, *, include_item_results: bool = False) -> str: # Or create summary report summary = result.format() # Aggregate view only - print(f"Experiment Summary:\\n{summary}") + print(f"Experiment Summary:\n{summary}") ``` Integration with logging systems: @@ -467,11 +467,11 @@ def format(self, *, include_item_results: bool = False) -> str: logger = logging.getLogger("experiments") # Log summary after experiment - logger.info(f"Experiment completed:\\n{result.format()}") + logger.info(f"Experiment completed:\n{result.format()}") # Log detailed results for failed experiments if any(eval['value'] < threshold for eval in result.run_evaluations): - logger.warning(f"Poor performance detected:\\n{result.format(include_item_results=True)}") + logger.warning(f"Poor performance detected:\n{result.format(include_item_results=True)}") ``` """ if not self.item_results: @@ -482,7 +482,7 @@ def format(self, *, include_item_results: bool = False) -> str: # Individual results section if include_item_results: for i, result in enumerate(self.item_results): - output += f"\\n{i + 1}. Item {i + 1}:\\n" + output += f"\n{i + 1}. Item {i + 1}:\n" # Extract and display input item_input = None @@ -492,7 +492,7 @@ def format(self, *, include_item_results: bool = False) -> str: item_input = result.item.input if item_input is not None: - output += f" Input: {_format_value(item_input)}\\n" + output += f" Input: {_format_value(item_input)}\n" # Extract and display expected output expected_output = None @@ -502,36 +502,36 @@ def format(self, *, include_item_results: bool = False) -> str: expected_output = result.item.expected_output if expected_output is not None: - output += f" Expected: {_format_value(expected_output)}\\n" - output += f" Actual: {_format_value(result.output)}\\n" + output += f" Expected: {_format_value(expected_output)}\n" + output += f" Actual: {_format_value(result.output)}\n" # Display evaluation scores if result.evaluations: - output += " Scores:\\n" + output += " Scores:\n" for evaluation in result.evaluations: score = evaluation.value if isinstance(score, (int, float)): score = f"{score:.3f}" output += f" • {evaluation.name}: {score}" if evaluation.comment: - output += f"\\n 💭 {evaluation.comment}" - output += "\\n" + output += f"\n 💭 {evaluation.comment}" + output += "\n" # Display trace link if available if result.trace_id: - output += f"\\n Trace ID: {result.trace_id}\\n" + output += f"\n Trace ID: {result.trace_id}\n" else: - output += f"Individual Results: Hidden ({len(self.item_results)} items)\\n" - output += "💡 Set include_item_results=True to view them\\n" + output += f"Individual Results: Hidden ({len(self.item_results)} items)\n" + output += "💡 Set include_item_results=True to view them\n" # Experiment overview section - output += f"\\n{'─' * 50}\\n" + output += f"\n{'─' * 50}\n" output += f"🧪 Experiment: {self.name}" output += f"\n📋 Run name: {self.run_name}" if self.description: output += f" - {self.description}" - output += f"\\n{len(self.item_results)} items" + output += f"\n{len(self.item_results)} items" # Collect unique evaluation names across all items evaluation_names = set() @@ -540,14 +540,14 @@ def format(self, *, include_item_results: bool = False) -> str: evaluation_names.add(evaluation.name) if evaluation_names: - output += "\\nEvaluations:" + output += "\nEvaluations:" for eval_name in evaluation_names: - output += f"\\n • {eval_name}" - output += "\\n" + output += f"\n • {eval_name}" + output += "\n" # Calculate and display average scores if evaluation_names: - output += "\\nAverage Scores:" + output += "\nAverage Scores:" for eval_name in evaluation_names: scores = [] for result in self.item_results: @@ -559,24 +559,24 @@ def format(self, *, include_item_results: bool = False) -> str: if scores: avg = sum(scores) / len(scores) - output += f"\\n • {eval_name}: {avg:.3f}" - output += "\\n" + output += f"\n • {eval_name}: {avg:.3f}" + output += "\n" # Display run-level evaluations if self.run_evaluations: - output += "\\nRun Evaluations:" + output += "\nRun Evaluations:" for run_eval in self.run_evaluations: score = run_eval.value if isinstance(score, (int, float)): score = f"{score:.3f}" - output += f"\\n • {run_eval.name}: {score}" + output += f"\n • {run_eval.name}: {score}" if run_eval.comment: - output += f"\\n 💭 {run_eval.comment}" - output += "\\n" + output += f"\n 💭 {run_eval.comment}" + output += "\n" # Add dataset run URL if available if self.dataset_run_url: - output += f"\\n🔗 Dataset Run:\\n {self.dataset_run_url}" + output += f"\n🔗 Dataset Run:\n {self.dataset_run_url}" return output From 849db5eb028c00e5940b745b6585edcddbb75ded Mon Sep 17 00:00:00 2001 From: Br1an <932039080@qq.com> Date: Mon, 2 Mar 2026 17:46:51 +0800 Subject: [PATCH 199/296] fix(openai): remove unreachable None type assignments for OpenAI classes (#1546) The second try/except block that assigns None to AsyncOpenAI, AsyncAzureOpenAI, AzureOpenAI, and OpenAI is unreachable because: 1. The first try/except raises ModuleNotFoundError if openai is missing 2. Line 28 already imports from openai._types (v1.x only), so if execution reaches the second block, openai 1.x is installed Merge both imports into a single try/except so type checkers correctly infer these as their actual types instead of type | None. Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/openai.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 8ea396872..cb7ca3a3f 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -37,19 +37,12 @@ try: import openai + from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI # noqa: F401 except ImportError: raise ModuleNotFoundError( "Please install OpenAI to use this feature: 'pip install openai'" ) -try: - from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI # noqa: F401 -except ImportError: - AsyncAzureOpenAI = None # type: ignore - AsyncOpenAI = None # type: ignore - AzureOpenAI = None # type: ignore - OpenAI = None # type: ignore - log = logging.getLogger("langfuse") From 96e81891ee1a44b0e5a5d2f4ba8c186717d5d3ad Mon Sep 17 00:00:00 2001 From: DeerShark Date: Mon, 2 Mar 2026 17:48:55 +0800 Subject: [PATCH 200/296] fix(langchain): Add handling for invalid tool calls in CallbackHandler (#1550) Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/langchain/CallbackHandler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 7a907d5e7..0cd4dd133 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1057,6 +1057,13 @@ def _convert_message_to_dict(self, message: BaseMessage) -> Dict[str, Any]: and len(message.tool_calls) > 0 ): message_dict["tool_calls"] = message.tool_calls + + if ( + hasattr(message, "invalid_tool_calls") + and message.invalid_tool_calls is not None + and len(message.invalid_tool_calls) > 0 + ): + message_dict["invalid_tool_calls"] = message.invalid_tool_calls elif isinstance(message, SystemMessage): message_dict = {"role": "system", "content": message.content} From 9efcca02013d2fd6ce455087a5f53d76ac39c5c1 Mon Sep 17 00:00:00 2001 From: SangJin Moon <1128msj@gmail.com> Date: Mon, 2 Mar 2026 18:50:21 +0900 Subject: [PATCH 201/296] =?UTF-8?q?fix:=20apply=20stricter=20early=20routi?= =?UTF-8?q?ng=20for=20base64=20media=20to=20prevent=20SSE=20dat=E2=80=A6?= =?UTF-8?q?=20(#1544)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- langfuse/_task_manager/media_manager.py | 7 +- tests/test_media_manager.py | 116 ++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index c885f9ba4..dbbc3976b 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -86,7 +86,12 @@ def _process_data_recursively(data: Any, level: int) -> Any: return data - if isinstance(data, str) and data.startswith("data:"): + if ( + isinstance(data, str) + and data.startswith("data:") + and "," in data + and data.split(",", 1)[0].endswith(";base64") + ): media = LangfuseMedia( obj=data, base64_data_uri=data, diff --git a/tests/test_media_manager.py b/tests/test_media_manager.py index 7a10da7dc..68684fac4 100644 --- a/tests/test_media_manager.py +++ b/tests/test_media_manager.py @@ -6,6 +6,7 @@ import pytest from langfuse._task_manager.media_manager import MediaManager +from langfuse.media import LangfuseMedia def _upload_response(status_code: int, text: str = "") -> httpx.Response: @@ -78,3 +79,118 @@ def test_media_upload_gives_up_on_non_retryable_http_status(): assert httpx_client.put.call_count == 1 media_api.patch.assert_called_once() assert media_api.patch.call_args.kwargs["upload_http_status"] == 403 + + +def test_find_and_process_media_sse_done_passes_through(): + queue = Queue() + manager = MediaManager( + api_client=SimpleNamespace(media=Mock()), + httpx_client=Mock(), + media_upload_queue=queue, + ) + + data = "data: [DONE]" + result = manager._find_and_process_media( + data=data, trace_id="trace-id", observation_id=None, field="output" + ) + + assert result == data + assert queue.empty() + + +def test_find_and_process_media_sse_json_payload_passes_through(): + queue = Queue() + manager = MediaManager( + api_client=SimpleNamespace(media=Mock()), + httpx_client=Mock(), + media_upload_queue=queue, + ) + + plain_sse = 'data: {"choices": [{"delta": {"content": "hello"}}]}' + result = manager._find_and_process_media( + data=plain_sse, trace_id="trace-id", observation_id=None, field="output" + ) + assert result == plain_sse + + tricky_sse = 'data: {"text": "what is ;base64 encoding?", "count": 1}' + result = manager._find_and_process_media( + data=tricky_sse, trace_id="trace-id", observation_id=None, field="output" + ) + assert result == tricky_sse + assert queue.empty() + + +def test_find_and_process_media_valid_base64_uri_is_processed(): + queue = Queue() + manager = MediaManager( + api_client=SimpleNamespace(media=Mock()), + httpx_client=Mock(), + media_upload_queue=queue, + ) + + valid_base64_data_uri = ( + "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QBARXhpZgAA" + ) + result = manager._find_and_process_media( + data=valid_base64_data_uri, + trace_id="trace-id", + observation_id=None, + field="input", + ) + + assert isinstance(result, LangfuseMedia) + assert not queue.empty() + + +def test_find_and_process_media_data_uri_without_comma_passes_through(): + queue = Queue() + manager = MediaManager( + api_client=SimpleNamespace(media=Mock()), + httpx_client=Mock(), + media_upload_queue=queue, + ) + + data = "data:image/png;base64" + result = manager._find_and_process_media( + data=data, trace_id="trace-id", observation_id=None, field="input" + ) + + assert result == data + assert queue.empty() + + +def test_find_and_process_media_non_base64_data_uri_passes_through(): + queue = Queue() + manager = MediaManager( + api_client=SimpleNamespace(media=Mock()), + httpx_client=Mock(), + media_upload_queue=queue, + ) + + data = "data:text/plain,hello world" + result = manager._find_and_process_media( + data=data, trace_id="trace-id", observation_id=None, field="input" + ) + + assert result == data + assert queue.empty() + + +def test_find_and_process_media_sse_in_nested_structure_passes_through(): + queue = Queue() + manager = MediaManager( + api_client=SimpleNamespace(media=Mock()), + httpx_client=Mock(), + media_upload_queue=queue, + ) + + data = [ + {"role": "assistant", "content": "data: [DONE]"}, + {"role": "user", "content": "data: hello"}, + ] + result = manager._find_and_process_media( + data=data, trace_id="trace-id", observation_id=None, field="output" + ) + + assert result == data + assert queue.empty() From e5f954f373c1c5aabad1a9ee07109bfb67ba89d8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 5 Mar 2026 14:16:52 +0200 Subject: [PATCH 202/296] chore: unify logging usage (#1539) --- langfuse/_client/observe.py | 6 +-- langfuse/_task_manager/media_manager.py | 22 +++++----- .../_task_manager/media_upload_consumer.py | 8 ++-- .../_task_manager/score_ingestion_consumer.py | 16 ++++---- langfuse/_utils/__init__.py | 3 -- langfuse/_utils/error_logging.py | 3 +- langfuse/_utils/parse_error.py | 12 +++--- langfuse/_utils/prompt_cache.py | 28 +++++-------- langfuse/_utils/request.py | 15 +++---- langfuse/batch_evaluation.py | 40 +++++++++---------- langfuse/experiment.py | 4 +- langfuse/media.py | 17 ++++---- langfuse/openai.py | 8 ++-- tests/test_otel.py | 10 +++-- 14 files changed, 83 insertions(+), 109 deletions(-) diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index e8786a0e0..6252c5383 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -1,7 +1,6 @@ import asyncio import contextvars import inspect -import logging import os from functools import wraps from typing import ( @@ -42,6 +41,7 @@ LangfuseSpan, LangfuseTool, ) +from langfuse.logger import langfuse_logger as logger from langfuse.types import TraceContext F = TypeVar("F", bound=Callable[..., Any]) @@ -69,8 +69,6 @@ class LangfuseDecorator: - Thread-safe client resolution when multiple Langfuse projects are used """ - _log = logging.getLogger("langfuse") - @overload def observe(self, func: F) -> F: ... @@ -166,7 +164,7 @@ def sub_process(): """ valid_types = set(get_observation_types_list(ObservationTypeLiteralNoEvent)) if as_type is not None and as_type not in valid_types: - self._log.warning( + logger.warning( f"Invalid as_type '{as_type}'. Valid types are: {', '.join(sorted(valid_types))}. Defaulting to 'span'." ) as_type = "span" diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index dbbc3976b..d1da17a27 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -1,4 +1,3 @@ -import logging import os import time from queue import Empty, Full, Queue @@ -12,6 +11,7 @@ from langfuse._utils import _get_timestamp from langfuse.api import LangfuseAPI, MediaContentType from langfuse.api.core import ApiError +from langfuse.logger import langfuse_logger as logger from langfuse.media import LangfuseMedia from .media_upload_queue import UploadMediaJob @@ -21,8 +21,6 @@ class MediaManager: - _log = logging.getLogger("langfuse") - def __init__( self, *, @@ -42,7 +40,7 @@ def __init__( def process_next_media_upload(self) -> None: try: upload_job = self._queue.get(block=True, timeout=1) - self._log.debug( + logger.debug( f"Media: Processing upload for media_id={upload_job['media_id']} in trace_id={upload_job['trace_id']}" ) self._process_upload_media_job(data=upload_job) @@ -51,7 +49,7 @@ def process_next_media_upload(self) -> None: except Empty: pass except Exception as e: - self._log.error( + logger.error( f"Media upload error: Failed to upload media due to unexpected error. Queue item marked as done. Error: {e}" ) self._queue.task_done() @@ -184,7 +182,7 @@ def _process_media( return if media._media_id is None: - self._log.error("Media ID is None. Skipping upload.") + logger.error("Media ID is None. Skipping upload.") return try: @@ -203,17 +201,17 @@ def _process_media( item=upload_media_job, block=False, ) - self._log.debug( + logger.debug( f"Queue: Enqueued media ID {media._media_id} for upload processing | trace_id={trace_id} | field={field}" ) except Full: - self._log.warning( + logger.warning( f"Queue capacity: Media queue is full. Failed to process media_id={media._media_id} for trace_id={trace_id}. Consider increasing queue capacity." ) except Exception as e: - self._log.error( + logger.error( f"Media processing error: Failed to process media_id={media._media_id} for trace_id={trace_id}. Error: {str(e)}" ) @@ -235,14 +233,14 @@ def _process_upload_media_job( upload_url = upload_url_response.upload_url if not upload_url: - self._log.debug( + logger.debug( f"Media status: Media with ID {data['media_id']} already uploaded. Skipping duplicate upload." ) return if upload_url_response.media_id != data["media_id"]: - self._log.error( + logger.error( f"Media integrity error: Media ID mismatch between SDK ({data['media_id']}) and Server ({upload_url_response.media_id}). Upload cancelled. Please check media ID generation logic." ) @@ -298,7 +296,7 @@ def _upload_with_status_check() -> httpx.Response: upload_time_ms=upload_time_ms, ) - self._log.debug( + logger.debug( f"Media upload: Successfully uploaded media_id={data['media_id']} for trace_id={data['trace_id']} | status_code={upload_response.status_code} | duration={upload_time_ms}ms | size={data['content_length']} bytes" ) diff --git a/langfuse/_task_manager/media_upload_consumer.py b/langfuse/_task_manager/media_upload_consumer.py index 182170864..b9058066b 100644 --- a/langfuse/_task_manager/media_upload_consumer.py +++ b/langfuse/_task_manager/media_upload_consumer.py @@ -1,11 +1,11 @@ -import logging import threading +from langfuse.logger import langfuse_logger as logger + from .media_manager import MediaManager class MediaUploadConsumer(threading.Thread): - _log = logging.getLogger("langfuse") _identifier: int _max_retries: int _media_manager: MediaManager @@ -30,7 +30,7 @@ def __init__( def run(self) -> None: """Run the media upload consumer.""" - self._log.debug( + logger.debug( f"Thread: Media upload consumer thread #{self._identifier} started and actively processing queue items" ) while self.running: @@ -38,7 +38,7 @@ def run(self) -> None: def pause(self) -> None: """Pause the media upload consumer.""" - self._log.debug( + logger.debug( f"Thread: Pausing media upload consumer thread #{self._identifier}" ) self.running = False diff --git a/langfuse/_task_manager/score_ingestion_consumer.py b/langfuse/_task_manager/score_ingestion_consumer.py index f53ea08f0..0aedaa7a5 100644 --- a/langfuse/_task_manager/score_ingestion_consumer.py +++ b/langfuse/_task_manager/score_ingestion_consumer.py @@ -1,5 +1,4 @@ import json -import logging import os import threading import time @@ -12,6 +11,7 @@ from langfuse._utils.parse_error import handle_exception from langfuse._utils.request import APIError, LangfuseClient from langfuse._utils.serializer import EventSerializer +from langfuse.logger import langfuse_logger as logger from ..version import __version__ as langfuse_version @@ -27,8 +27,6 @@ class ScoreIngestionMetadata(BaseModel): class ScoreIngestionConsumer(threading.Thread): - _log = logging.getLogger("langfuse") - def __init__( self, *, @@ -83,7 +81,7 @@ def _next(self) -> list: try: json.dumps(event, cls=EventSerializer) except Exception as e: - self._log.error( + logger.error( f"Data error: Failed to serialize score object for ingestion. Score will be dropped. Error: {e}" ) self._ingestion_queue.task_done() @@ -94,7 +92,7 @@ def _next(self) -> list: total_size += item_size if total_size >= MAX_BATCH_SIZE_BYTES: - self._log.debug( + logger.debug( f"Batch management: Reached maximum batch size limit ({total_size} bytes). Processing {len(events)} events now." ) break @@ -103,7 +101,7 @@ def _next(self) -> list: break except Exception as e: - self._log.warning( + logger.warning( f"Data processing error: Failed to process score event in consumer thread #{self._identifier}. Event will be dropped. Error: {str(e)}", exc_info=True, ) @@ -117,7 +115,7 @@ def _get_item_size(self, item: Any) -> int: def run(self) -> None: """Run the consumer.""" - self._log.debug( + logger.debug( f"Startup: Score ingestion consumer thread #{self._identifier} started with batch size {self._flush_at} and interval {self._flush_interval}s" ) while self.running: @@ -143,7 +141,7 @@ def pause(self) -> None: self.running = False def _upload_batch(self, batch: List[Any]) -> None: - self._log.debug( + logger.debug( f"API: Uploading batch of {len(batch)} score events to Langfuse API" ) @@ -171,6 +169,6 @@ def execute_task_with_backoff(batch: List[Any]) -> None: raise e execute_task_with_backoff(batch) - self._log.debug( + logger.debug( f"API: Successfully sent {len(batch)} score events to Langfuse API in batch mode" ) diff --git a/langfuse/_utils/__init__.py b/langfuse/_utils/__init__.py index e8a02abc1..5be7655c1 100644 --- a/langfuse/_utils/__init__.py +++ b/langfuse/_utils/__init__.py @@ -1,13 +1,10 @@ """@private""" -import logging import typing from datetime import datetime, timezone from langfuse.model import PromptClient -log = logging.getLogger("langfuse") - def _get_timestamp() -> datetime: return datetime.now(timezone.utc) diff --git a/langfuse/_utils/error_logging.py b/langfuse/_utils/error_logging.py index e5a7fe67c..4e3abac92 100644 --- a/langfuse/_utils/error_logging.py +++ b/langfuse/_utils/error_logging.py @@ -1,8 +1,7 @@ import functools -import logging from typing import Any, Callable, List, Optional -logger = logging.getLogger("langfuse") +from langfuse.logger import langfuse_logger as logger def catch_and_log_errors(func: Callable[..., Any]) -> Callable[..., Any]: diff --git a/langfuse/_utils/parse_error.py b/langfuse/_utils/parse_error.py index 4fd8d6a69..2b9a7bd6f 100644 --- a/langfuse/_utils/parse_error.py +++ b/langfuse/_utils/parse_error.py @@ -1,4 +1,3 @@ -import logging from typing import Union # our own api errors @@ -14,6 +13,7 @@ UnauthorizedError, ) from langfuse.api.core import ApiError +from langfuse.logger import langfuse_logger as logger SUPPORT_URL = "https://langfuse.com/support" API_DOCS_URL = "https://api.reference.langfuse.com" @@ -67,10 +67,9 @@ def generate_error_message_fern(error: Error) -> str: def handle_fern_exception(exception: Error) -> None: - log = logging.getLogger("langfuse") - log.debug(exception) + logger.debug(exception) error_message = generate_error_message_fern(exception) - log.error(error_message) + logger.error(error_message) def generate_error_message(exception: Union[APIError, APIErrors, Exception]) -> str: @@ -95,7 +94,6 @@ def generate_error_message(exception: Union[APIError, APIErrors, Exception]) -> def handle_exception(exception: Union[APIError, APIErrors, Exception]) -> None: - log = logging.getLogger("langfuse") - log.debug(exception) + logger.debug(exception) error_message = generate_error_message(exception) - log.error(error_message) + logger.error(error_message) diff --git a/langfuse/_utils/prompt_cache.py b/langfuse/_utils/prompt_cache.py index 09d78bc8b..ef465b038 100644 --- a/langfuse/_utils/prompt_cache.py +++ b/langfuse/_utils/prompt_cache.py @@ -1,7 +1,6 @@ """@private""" import atexit -import logging import os from datetime import datetime from queue import Empty, Queue @@ -11,6 +10,7 @@ from langfuse._client.environment_variables import ( LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS, ) +from langfuse.logger import langfuse_logger as logger from langfuse.model import PromptClient DEFAULT_PROMPT_CACHE_TTL_SECONDS = int( @@ -34,7 +34,6 @@ def get_epoch_seconds() -> int: class PromptCacheRefreshConsumer(Thread): - _log = logging.getLogger("langfuse") _queue: Queue _identifier: int running: bool = True @@ -49,14 +48,14 @@ def run(self) -> None: while self.running: try: task = self._queue.get(timeout=1) - self._log.debug( + logger.debug( f"PromptCacheRefreshConsumer processing task, {self._identifier}" ) try: task() # Task failed, but we still consider it processed except Exception as e: - self._log.warning( + logger.warning( f"PromptCacheRefreshConsumer encountered an error, cache was not refreshed: {self._identifier}, {e}" ) @@ -70,7 +69,6 @@ def pause(self) -> None: class PromptCacheTaskManager(object): - _log = logging.getLogger("langfuse") _consumers: List[PromptCacheRefreshConsumer] _threads: int _queue: Queue @@ -91,31 +89,29 @@ def __init__(self, threads: int = 1): def add_task(self, key: str, task: Callable[[], None]) -> None: if key not in self._processing_keys: - self._log.debug(f"Adding prompt cache refresh task for key: {key}") + logger.debug(f"Adding prompt cache refresh task for key: {key}") self._processing_keys.add(key) wrapped_task = self._wrap_task(key, task) self._queue.put((wrapped_task)) else: - self._log.debug( - f"Prompt cache refresh task already submitted for key: {key}" - ) + logger.debug(f"Prompt cache refresh task already submitted for key: {key}") def active_tasks(self) -> int: return len(self._processing_keys) def _wrap_task(self, key: str, task: Callable[[], None]) -> Callable[[], None]: def wrapped() -> None: - self._log.debug(f"Refreshing prompt cache for key: {key}") + logger.debug(f"Refreshing prompt cache for key: {key}") try: task() finally: self._processing_keys.remove(key) - self._log.debug(f"Refreshed prompt cache for key: {key}") + logger.debug(f"Refreshed prompt cache for key: {key}") return wrapped def shutdown(self) -> None: - self._log.debug( + logger.debug( f"Shutting down prompt refresh task manager, {len(self._consumers)} consumers,..." ) @@ -131,7 +127,7 @@ def shutdown(self) -> None: # consumer thread has not started pass - self._log.debug("Shutdown of prompt refresh task manager completed.") + logger.debug("Shutdown of prompt refresh task manager completed.") class PromptCache: @@ -140,14 +136,12 @@ class PromptCache: _task_manager: PromptCacheTaskManager """Task manager for refreshing cache""" - _log = logging.getLogger("langfuse") - def __init__( self, max_prompt_refresh_workers: int = DEFAULT_PROMPT_CACHE_REFRESH_WORKERS ): self._cache = {} self._task_manager = PromptCacheTaskManager(threads=max_prompt_refresh_workers) - self._log.debug("Prompt cache initialized.") + logger.debug("Prompt cache initialized.") def get(self, key: str) -> Optional[PromptCacheItem]: return self._cache.get(key, None) @@ -168,7 +162,7 @@ def invalidate(self, prompt_name: str) -> None: del self._cache[key] def add_refresh_prompt_task(self, key: str, fetch_func: Callable[[], None]) -> None: - self._log.debug(f"Submitting refresh task for key: {key}") + logger.debug(f"Submitting refresh task for key: {key}") self._task_manager.add_task(key, fetch_func) def clear(self) -> None: diff --git a/langfuse/_utils/request.py b/langfuse/_utils/request.py index 182fe3ffe..104552ee7 100644 --- a/langfuse/_utils/request.py +++ b/langfuse/_utils/request.py @@ -1,13 +1,13 @@ """@private""" import json -import logging from base64 import b64encode from typing import Any, List, Union import httpx from langfuse._utils.serializer import EventSerializer +from langfuse.logger import langfuse_logger as logger class LangfuseClient: @@ -48,8 +48,7 @@ def generate_headers(self) -> dict: def batch_post(self, **kwargs: Any) -> httpx.Response: """Post the `kwargs` to the batch API endpoint for events""" - log = logging.getLogger("langfuse") - log.debug("uploading data: %s", kwargs) + logger.debug("uploading data: %s", kwargs) res = self.post(**kwargs) return self._process_response( @@ -58,17 +57,16 @@ def batch_post(self, **kwargs: Any) -> httpx.Response: def post(self, **kwargs: Any) -> httpx.Response: """Post the `kwargs` to the API""" - log = logging.getLogger("langfuse") url = self._remove_trailing_slash(self._base_url) + "/api/public/ingestion" data = json.dumps(kwargs, cls=EventSerializer) - log.debug("making request: %s to %s", data, url) + logger.debug("making request: %s to %s", data, url) headers = self.generate_headers() res = self._session.post( url, content=data, headers=headers, timeout=self._timeout ) if res.status_code == 200: - log.debug("data uploaded successfully") + logger.debug("data uploaded successfully") return res @@ -81,10 +79,9 @@ def _remove_trailing_slash(self, url: str) -> str: def _process_response( self, res: httpx.Response, success_message: str, *, return_json: bool = True ) -> Union[httpx.Response, Any]: - log = logging.getLogger("langfuse") - log.debug("received response: %s", res.text) + logger.debug("received response: %s", res.text) if res.status_code in (200, 201): - log.debug(success_message) + logger.debug(success_message) if return_json: try: return res.json() diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 45cf25c04..0ed8a7458 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -8,7 +8,6 @@ import asyncio import json -import logging import time from typing import ( TYPE_CHECKING, @@ -29,12 +28,11 @@ TraceWithFullDetails, ) from langfuse.experiment import Evaluation, EvaluatorFunction +from langfuse.logger import langfuse_logger as logger if TYPE_CHECKING: from langfuse._client.client import Langfuse -logger = logging.getLogger("langfuse") - class EvaluatorInputs: """Input data structure for evaluators, returned by mapper functions. @@ -828,7 +826,6 @@ class BatchEvaluationRunner: Attributes: client: The Langfuse client instance used for API calls and score creation. - _log: Logger instance for this runner. """ def __init__(self, client: "Langfuse"): @@ -838,7 +835,6 @@ def __init__(self, client: "Langfuse"): client: The Langfuse client instance. """ self.client = client - self._log = logger async def run_async( self, @@ -927,11 +923,11 @@ async def run_async( last_item_id: Optional[str] = None if verbose: - self._log.info(f"Starting batch evaluation on {scope}") + logger.info(f"Starting batch evaluation on {scope}") if scope == "traces" and fetch_trace_fields: - self._log.info(f"Fetching trace fields: {fetch_trace_fields}") + logger.info(f"Fetching trace fields: {fetch_trace_fields}") if resume_from: - self._log.info( + logger.info( f"Resuming from {resume_from.last_processed_timestamp} " f"({resume_from.items_processed} items already processed)" ) @@ -941,7 +937,7 @@ async def run_async( # Check if we've reached max_items if max_items is not None and total_items_fetched >= max_items: if verbose: - self._log.info(f"Reached max_items limit ({max_items})") + logger.info(f"Reached max_items limit ({max_items})") has_more = True # More items may exist break @@ -958,7 +954,7 @@ async def run_async( except Exception as e: # Failed after max_retries - create resume token and return error_msg = f"Failed to fetch batch after {max_retries} retries" - self._log.error(f"{error_msg}: {e}") + logger.error(f"{error_msg}: {e}") resume_token = BatchEvaluationResumeToken( scope=scope, @@ -989,13 +985,13 @@ async def run_async( if not items: has_more = False if verbose: - self._log.info("No more items to fetch") + logger.info("No more items to fetch") break total_items_fetched += len(items) if verbose: - self._log.info(f"Fetched batch {page} ({len(items)} items)") + logger.info(f"Fetched batch {page} ({len(items)} items)") # Limit items if max_items would be exceeded items_to_process = items @@ -1004,7 +1000,7 @@ async def run_async( if len(items) > remaining_capacity: items_to_process = items[:remaining_capacity] if verbose: - self._log.info( + logger.info( f"Limiting batch to {len(items_to_process)} items " f"to respect max_items={max_items}" ) @@ -1043,7 +1039,7 @@ async def process_item( failed_item_ids.append(item_id) error_type = type(result).__name__ error_summary[error_type] = error_summary.get(error_type, 0) + 1 - self._log.warning(f"Item {item_id} failed: {result}") + logger.warning(f"Item {item_id} failed: {result}") else: # Item processed successfully total_items_processed += 1 @@ -1078,12 +1074,12 @@ async def process_item( if verbose: if max_items is not None and max_items > 0: progress_pct = total_items_processed / max_items * 100 - self._log.info( + logger.info( f"Progress: {total_items_processed}/{max_items} items " f"({progress_pct:.1f}%), {total_scores_created} scores created" ) else: - self._log.info( + logger.info( f"Progress: {total_items_processed} items processed, " f"{total_scores_created} scores created" ) @@ -1102,14 +1098,14 @@ async def process_item( # Flush all scores to Langfuse if verbose: - self._log.info("Flushing scores to Langfuse...") + logger.info("Flushing scores to Langfuse...") self.client.flush() # Build final result duration = time.time() - start_time if verbose: - self._log.info( + logger.info( f"Batch evaluation complete: {total_items_processed} items processed " f"in {duration:.2f}s" ) @@ -1249,7 +1245,7 @@ async def _process_batch_evaluation_item( # Evaluator failed - log warning and continue with other evaluators stats.failed_runs += 1 evaluations_failed += 1 - self._log.warning( + logger.warning( f"Evaluator {evaluator_name} failed on item " f"{self._get_item_id(item, scope)}: {e}" ) @@ -1297,7 +1293,7 @@ async def _process_batch_evaluation_item( evaluations.extend(composite_evals) except Exception as e: - self._log.warning(f"Composite evaluator failed on item {item_id}: {e}") + logger.warning(f"Composite evaluator failed on item {item_id}: {e}") return ( scores_created, @@ -1496,12 +1492,12 @@ def _build_timestamp_filter( try: filter_list = json.loads(original_filter) if original_filter else [] if not isinstance(filter_list, list): - self._log.warning( + logger.warning( f"Filter should be a JSON array, got: {type(filter_list).__name__}" ) filter_list = [] except json.JSONDecodeError: - self._log.warning( + logger.warning( f"Invalid JSON in original filter, ignoring: {original_filter}" ) filter_list = [] diff --git a/langfuse/experiment.py b/langfuse/experiment.py index ee9f9eaa7..ff707fc9b 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -6,7 +6,6 @@ """ import asyncio -import logging from typing import ( Any, Awaitable, @@ -19,6 +18,7 @@ ) from langfuse.api import DatasetItem, ScoreDataType +from langfuse.logger import langfuse_logger as logger class LocalExperimentItem(TypedDict, total=False): @@ -994,7 +994,7 @@ async def _run_evaluator( except Exception as e: evaluator_name = getattr(evaluator, "__name__", "unknown_evaluator") - logging.getLogger("langfuse").error(f"Evaluator {evaluator_name} failed: {e}") + logger.error(f"Evaluator {evaluator_name} failed: {e}") return [] diff --git a/langfuse/media.py b/langfuse/media.py index c87626785..53940382c 100644 --- a/langfuse/media.py +++ b/langfuse/media.py @@ -2,19 +2,19 @@ import base64 import hashlib -import logging import os import re from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple, TypeVar, cast import httpx -if TYPE_CHECKING: - from langfuse._client.client import Langfuse - from langfuse.api import MediaContentType +from langfuse.logger import langfuse_logger as logger from langfuse.types import ParsedMediaReference +if TYPE_CHECKING: + from langfuse._client.client import Langfuse + T = TypeVar("T") @@ -40,7 +40,6 @@ class LangfuseMedia: obj: object - _log = logging.getLogger(__name__) _content_bytes: Optional[bytes] _content_type: Optional[MediaContentType] _source: Optional[str] @@ -87,7 +86,7 @@ def __init__( self._content_type = content_type if self._content_bytes else None self._source = "file" if self._content_bytes else None else: - self._log.error( + logger.error( "base64_data_uri, or content_bytes and content_type, or file_path must be provided to LangfuseMedia" ) @@ -102,7 +101,7 @@ def _read_file(self, file_path: str) -> Optional[bytes]: with open(file_path, "rb") as file: return file.read() except Exception as e: - self._log.error(f"Error reading file at path {file_path}", exc_info=e) + logger.error(f"Error reading file at path {file_path}", exc_info=e) return None @@ -218,7 +217,7 @@ def _parse_base64_data_uri( return base64.b64decode(actual_data), cast(MediaContentType, content_type) except Exception as e: - self._log.error("Error parsing base64 data URI", exc_info=e) + logger.error("Error parsing base64 data URI", exc_info=e) return None, None @@ -319,7 +318,7 @@ def traverse(obj: Any, depth: int) -> Any: base64_data_uri ) except Exception as e: - LangfuseMedia._log.warning( + logger.warning( f"Error fetching media content for reference string {reference_string}: {e}" ) # Do not replace the reference string if there's an error diff --git a/langfuse/openai.py b/langfuse/openai.py index cb7ca3a3f..057d90cab 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -17,7 +17,6 @@ See docs for more details: https://langfuse.com/docs/integrations/openai """ -import logging import types from collections import defaultdict from dataclasses import dataclass @@ -33,6 +32,7 @@ from langfuse._client.get_client import get_client from langfuse._client.span import LangfuseGeneration from langfuse._utils import _get_timestamp +from langfuse.logger import langfuse_logger as logger from langfuse.media import LangfuseMedia try: @@ -43,8 +43,6 @@ "Please install OpenAI to use this feature: 'pip install openai'" ) -log = logging.getLogger("langfuse") - @dataclass class OpenAiDefinition: @@ -863,7 +861,7 @@ def _wrap( return openai_response except Exception as ex: - log.warning(ex) + logger.warning(ex) model = kwargs.get("model", None) or None generation.update( status_message=str(ex), @@ -934,7 +932,7 @@ async def _wrap_async( return openai_response except Exception as ex: - log.warning(ex) + logger.warning(ex) model = kwargs.get("model", None) or None generation.update( status_message=str(ex), diff --git a/tests/test_otel.py b/tests/test_otel.py index c0f42fc73..b4b985780 100644 --- a/tests/test_otel.py +++ b/tests/test_otel.py @@ -2582,10 +2582,12 @@ def test_custom_should_export_span_with_composition( public_key=instrumentation_filtering_setup["test_key"], secret_key="test-secret-key", base_url="http://localhost:3000", - should_export_span=lambda span: is_default_export_span(span) - or ( - span.instrumentation_scope is not None - and span.instrumentation_scope.name.startswith("my-framework") + should_export_span=lambda span: ( + is_default_export_span(span) + or ( + span.instrumentation_scope is not None + and span.instrumentation_scope.name.startswith("my-framework") + ) ), ) From 63a3b620e906e8997b45696f794541920091b973 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:18:39 +0100 Subject: [PATCH 203/296] fix(client): pass release variable to span clients (#1518) --- langfuse/_client/client.py | 14 ++++++++++++++ langfuse/_client/span.py | 20 ++++++++++++++++++++ tests/test_resource_manager.py | 1 + 3 files changed, 35 insertions(+) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 21f0ba394..a8781c9b9 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -52,6 +52,7 @@ LANGFUSE_DEBUG, LANGFUSE_HOST, LANGFUSE_PUBLIC_KEY, + LANGFUSE_RELEASE, LANGFUSE_SAMPLE_RATE, LANGFUSE_SECRET_KEY, LANGFUSE_TIMEOUT, @@ -77,6 +78,7 @@ ) from langfuse._client.utils import get_sha256_hash_hex, run_async_safely from langfuse._utils import _get_timestamp +from langfuse._utils.environment import get_common_release_envs from langfuse._utils.parse_error import handle_fern_exception from langfuse._utils.prompt_cache import PromptCache from langfuse.api import ( @@ -252,6 +254,11 @@ def __init__( self._environment = environment or cast( str, os.environ.get(LANGFUSE_TRACING_ENVIRONMENT) ) + self._release = ( + release + or os.environ.get(LANGFUSE_RELEASE, None) + or get_common_release_envs() + ) self._project_id: Optional[str] = None sample_rate = sample_rate or float(os.environ.get(LANGFUSE_SAMPLE_RATE, 1.0)) if not 0.0 <= sample_rate <= 1.0: @@ -633,6 +640,7 @@ def _create_observation_from_otel_span( otel_span=otel_span, langfuse_client=self, environment=self._environment, + release=self._release, input=input, output=output, metadata=metadata, @@ -655,6 +663,7 @@ def _create_observation_from_otel_span( otel_span=otel_span, langfuse_client=self, environment=self._environment, + release=self._release, input=input, output=output, metadata=metadata, @@ -1168,6 +1177,7 @@ def _start_as_current_otel_span_with_processed_media( "otel_span": otel_span, "langfuse_client": self, "environment": self._environment, + "release": self._release, "input": input, "output": output, "metadata": metadata, @@ -1346,6 +1356,7 @@ def update_current_span( otel_span=current_otel_span, langfuse_client=self, environment=self._environment, + release=self._release, ) if name: @@ -1403,6 +1414,7 @@ def set_current_trace_io( otel_span=current_otel_span, langfuse_client=self, environment=self._environment, + release=self._release, ) span.set_trace_io( @@ -1502,6 +1514,7 @@ def create_event( otel_span=otel_span, langfuse_client=self, environment=self._environment, + release=self._release, input=input, output=output, metadata=metadata, @@ -1519,6 +1532,7 @@ def create_event( otel_span=otel_span, langfuse_client=self, environment=self._environment, + release=self._release, input=input, output=output, metadata=metadata, diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index a86b8c14a..2590262ce 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -85,6 +85,7 @@ def __init__( output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, + release: Optional[str] = None, version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, @@ -105,6 +106,7 @@ def __init__( output: Output data from the span (any JSON-serializable object) metadata: Additional metadata to associate with the span environment: The tracing environment + release: Release identifier for the application version: Version identifier for the code or component level: Importance level of the span (info, warning, error) status_message: Optional status message for the span @@ -131,6 +133,12 @@ def __init__( LangfuseOtelSpanAttributes.ENVIRONMENT, self._environment ) + self._release = release or self._langfuse_client._release + if self._release is not None: + self._otel_span.set_attribute( + LangfuseOtelSpanAttributes.RELEASE, self._release + ) + # Handle media only if span is sampled if self._otel_span.is_recording(): media_processed_input = self._process_media_and_apply_mask( @@ -925,6 +933,7 @@ def start_observation( output=output, metadata=metadata, environment=self._environment, + release=self._release, version=version, level=level, status_message=status_message, @@ -945,6 +954,7 @@ def start_observation( "otel_span": new_otel_span, "langfuse_client": self._langfuse_client, "environment": self._environment, + "release": self._release, "input": input, "output": output, "metadata": metadata, @@ -1225,6 +1235,7 @@ def create_event( output=output, metadata=metadata, environment=self._environment, + release=self._release, version=version, level=level, status_message=status_message, @@ -1251,6 +1262,7 @@ def __init__( output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, + release: Optional[str] = None, version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, @@ -1264,6 +1276,7 @@ def __init__( output: Output data from the span (any JSON-serializable object) metadata: Additional metadata to associate with the span environment: The tracing environment + release: Release identifier for the application version: Version identifier for the code or component level: Importance level of the span (info, warning, error) status_message: Optional status message for the span @@ -1276,6 +1289,7 @@ def __init__( output=output, metadata=metadata, environment=environment, + release=release, version=version, level=level, status_message=status_message, @@ -1299,6 +1313,7 @@ def __init__( output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, + release: Optional[str] = None, version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, @@ -1318,6 +1333,7 @@ def __init__( output: Output from the generation (e.g., completions) metadata: Additional metadata to associate with the generation environment: The tracing environment + release: Release identifier for the application version: Version identifier for the model or component level: Importance level of the generation (info, warning, error) status_message: Optional status message for the generation @@ -1336,6 +1352,7 @@ def __init__( output=output, metadata=metadata, environment=environment, + release=release, version=version, level=level, status_message=status_message, @@ -1360,6 +1377,7 @@ def __init__( output: Optional[Any] = None, metadata: Optional[Any] = None, environment: Optional[str] = None, + release: Optional[str] = None, version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, @@ -1373,6 +1391,7 @@ def __init__( output: Output from the event metadata: Additional metadata to associate with the generation environment: The tracing environment + release: Release identifier for the application version: Version identifier for the model or component level: Importance level of the generation (info, warning, error) status_message: Optional status message for the generation @@ -1385,6 +1404,7 @@ def __init__( output=output, metadata=metadata, environment=environment, + release=release, version=version, level=level, status_message=status_message, diff --git a/tests/test_resource_manager.py b/tests/test_resource_manager.py index c692db5ab..72f9f7d7e 100644 --- a/tests/test_resource_manager.py +++ b/tests/test_resource_manager.py @@ -27,6 +27,7 @@ def should_export(span): retrieved_client = get_client() assert retrieved_client._environment == settings["environment"] + assert retrieved_client._release == settings["release"] assert retrieved_client._resources is not None rm = retrieved_client._resources From 9f02a1693575e9080567c3b1ae511af1560e8d91 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 9 Mar 2026 20:55:22 +0100 Subject: [PATCH 204/296] feat(api): update API spec from langfuse/langfuse 2eaf041 (#1556) --- .github/workflows/ci.yml | 4 + langfuse/api/__init__.py | 65 +-- langfuse/api/client.py | 106 ++-- langfuse/api/legacy/__init__.py | 61 ++ langfuse/api/legacy/client.py | 105 ++++ .../metrics_v1}/__init__.py | 6 +- langfuse/api/legacy/metrics_v1/client.py | 214 +++++++ langfuse/api/legacy/metrics_v1/raw_client.py | 322 +++++++++++ .../metrics_v1}/types/__init__.py | 6 +- .../metrics_v1}/types/metrics_response.py | 2 +- .../observations_v1}/__init__.py | 8 +- .../observations_v1}/client.py | 283 +++++----- .../observations_v1}/raw_client.py | 416 +++++++++----- .../observations_v1}/types/__init__.py | 10 +- .../observations_v1}/types/observations.py | 6 +- .../types/observations_views.py | 6 +- langfuse/api/legacy/raw_client.py | 13 + .../{score => legacy/score_v1}/__init__.py | 0 .../api/{score => legacy/score_v1}/client.py | 34 +- .../{score => legacy/score_v1}/raw_client.py | 32 +- .../score_v1}/types/__init__.py | 0 .../score_v1}/types/create_score_request.py | 10 +- .../score_v1}/types/create_score_response.py | 2 +- langfuse/api/metrics/__init__.py | 6 +- langfuse/api/metrics/client.py | 278 +++++++-- langfuse/api/metrics/raw_client.py | 290 ++++++++-- langfuse/api/metrics/types/__init__.py | 6 +- .../types/metrics_v2response.py | 0 langfuse/api/metrics_v2/client.py | 422 -------------- langfuse/api/metrics_v2/raw_client.py | 530 ------------------ langfuse/api/observations/__init__.py | 8 +- langfuse/api/observations/client.py | 255 +++++---- langfuse/api/observations/raw_client.py | 384 +++++-------- langfuse/api/observations/types/__init__.py | 10 +- .../types/observations_v2meta.py | 0 .../types/observations_v2response.py | 0 langfuse/api/{score_v2 => scores}/__init__.py | 0 langfuse/api/{score_v2 => scores}/client.py | 34 +- .../api/{score_v2 => scores}/raw_client.py | 8 +- .../{score_v2 => scores}/types/__init__.py | 0 .../types/get_scores_response.py | 0 .../types/get_scores_response_data.py | 0 .../types/get_scores_response_data_boolean.py | 0 .../get_scores_response_data_categorical.py | 0 .../get_scores_response_data_correction.py | 0 .../types/get_scores_response_data_numeric.py | 0 .../types/get_scores_response_trace_data.py | 0 langfuse/batch_evaluation.py | 2 +- tests/test_core_sdk.py | 24 +- tests/test_openai.py | 76 +-- tests/test_prompt.py | 2 +- 51 files changed, 2089 insertions(+), 1957 deletions(-) create mode 100644 langfuse/api/legacy/__init__.py create mode 100644 langfuse/api/legacy/client.py rename langfuse/api/{metrics_v2 => legacy/metrics_v1}/__init__.py (87%) create mode 100644 langfuse/api/legacy/metrics_v1/client.py create mode 100644 langfuse/api/legacy/metrics_v1/raw_client.py rename langfuse/api/{metrics_v2 => legacy/metrics_v1}/types/__init__.py (85%) rename langfuse/api/{metrics => legacy/metrics_v1}/types/metrics_response.py (90%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/__init__.py (83%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/client.py (66%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/raw_client.py (65%) rename langfuse/api/{observations_v2 => legacy/observations_v1}/types/__init__.py (78%) rename langfuse/api/{observations => legacy/observations_v1}/types/observations.py (63%) rename langfuse/api/{observations => legacy/observations_v1}/types/observations_views.py (63%) create mode 100644 langfuse/api/legacy/raw_client.py rename langfuse/api/{score => legacy/score_v1}/__init__.py (100%) rename langfuse/api/{score => legacy/score_v1}/client.py (92%) rename langfuse/api/{score => legacy/score_v1}/raw_client.py (95%) rename langfuse/api/{score => legacy/score_v1}/types/__init__.py (100%) rename langfuse/api/{score => legacy/score_v1}/types/create_score_request.py (89%) rename langfuse/api/{score => legacy/score_v1}/types/create_score_response.py (85%) rename langfuse/api/{metrics_v2 => metrics}/types/metrics_v2response.py (100%) delete mode 100644 langfuse/api/metrics_v2/client.py delete mode 100644 langfuse/api/metrics_v2/raw_client.py rename langfuse/api/{observations_v2 => observations}/types/observations_v2meta.py (100%) rename langfuse/api/{observations_v2 => observations}/types/observations_v2response.py (100%) rename langfuse/api/{score_v2 => scores}/__init__.py (100%) rename langfuse/api/{score_v2 => scores}/client.py (95%) rename langfuse/api/{score_v2 => scores}/raw_client.py (99%) rename langfuse/api/{score_v2 => scores}/types/__init__.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_boolean.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_categorical.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_correction.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_data_numeric.py (100%) rename langfuse/api/{score_v2 => scores}/types/get_scores_response_trace_data.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9908e5caa..113733a84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,6 +126,10 @@ jobs: LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 \ LANGFUSE_INGESTION_QUEUE_DELAY_MS=10 \ LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=10 \ + LANGFUSE_EXPERIMENT_INSERT_INTO_EVENTS_TABLE=true \ + QUEUE_CONSUMER_EVENT_PROPAGATION_QUEUE_IS_ENABLED=true \ + LANGFUSE_ENABLE_EVENTS_TABLE_V2_APIS=true \ + LANGFUSE_ENABLE_EVENTS_TABLE_OBSERVATIONS=true \ docker compose up -d echo "::endgroup::" diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 8b70c0807..7322f3e71 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -16,22 +16,20 @@ datasets, health, ingestion, + legacy, llm_connections, media, metrics, - metrics_v2, models, observations, - observations_v2, opentelemetry, organizations, projects, prompt_version, prompts, scim, - score, score_configs, - score_v2, + scores, sessions, trace, utils, @@ -194,11 +192,9 @@ MediaContentType, PatchMediaBody, ) - from .metrics import MetricsResponse - from .metrics_v2 import MetricsV2Response + from .metrics import MetricsV2Response from .models import CreateModelRequest, PaginatedModels - from .observations import Observations, ObservationsViews - from .observations_v2 import ObservationsV2Meta, ObservationsV2Response + from .observations import ObservationsV2Meta, ObservationsV2Response from .opentelemetry import ( OtelAttribute, OtelAttributeValue, @@ -271,13 +267,12 @@ ServiceProviderConfig, UserMeta, ) - from .score import CreateScoreRequest, CreateScoreResponse from .score_configs import ( CreateScoreConfigRequest, ScoreConfigs, UpdateScoreConfigRequest, ) - from .score_v2 import ( + from .scores import ( GetScoresResponse, GetScoresResponseData, GetScoresResponseDataBoolean, @@ -348,8 +343,6 @@ "CreateObservationEvent": ".ingestion", "CreatePromptRequest": ".prompts", "CreateScoreConfigRequest": ".score_configs", - "CreateScoreRequest": ".score", - "CreateScoreResponse": ".score", "CreateScoreValue": ".commons", "CreateSpanBody": ".ingestion", "CreateSpanEvent": ".ingestion", @@ -374,17 +367,17 @@ "GetMediaResponse": ".media", "GetMediaUploadUrlRequest": ".media", "GetMediaUploadUrlResponse": ".media", - "GetScoresResponse": ".score_v2", - "GetScoresResponseData": ".score_v2", - "GetScoresResponseDataBoolean": ".score_v2", - "GetScoresResponseDataCategorical": ".score_v2", - "GetScoresResponseDataCorrection": ".score_v2", - "GetScoresResponseDataNumeric": ".score_v2", - "GetScoresResponseData_Boolean": ".score_v2", - "GetScoresResponseData_Categorical": ".score_v2", - "GetScoresResponseData_Correction": ".score_v2", - "GetScoresResponseData_Numeric": ".score_v2", - "GetScoresResponseTraceData": ".score_v2", + "GetScoresResponse": ".scores", + "GetScoresResponseData": ".scores", + "GetScoresResponseDataBoolean": ".scores", + "GetScoresResponseDataCategorical": ".scores", + "GetScoresResponseDataCorrection": ".scores", + "GetScoresResponseDataNumeric": ".scores", + "GetScoresResponseData_Boolean": ".scores", + "GetScoresResponseData_Categorical": ".scores", + "GetScoresResponseData_Correction": ".scores", + "GetScoresResponseData_Numeric": ".scores", + "GetScoresResponseTraceData": ".scores", "HealthResponse": ".health", "IngestionError": ".ingestion", "IngestionEvent": ".ingestion", @@ -412,8 +405,7 @@ "MembershipRole": ".organizations", "MembershipsResponse": ".organizations", "MethodNotAllowedError": ".commons", - "MetricsResponse": ".metrics", - "MetricsV2Response": ".metrics_v2", + "MetricsV2Response": ".metrics", "Model": ".commons", "ModelPrice": ".commons", "ModelUsageUnit": ".commons", @@ -425,11 +417,9 @@ "ObservationLevel": ".commons", "ObservationType": ".ingestion", "ObservationV2": ".commons", - "Observations": ".observations", - "ObservationsV2Meta": ".observations_v2", - "ObservationsV2Response": ".observations_v2", + "ObservationsV2Meta": ".observations", + "ObservationsV2Response": ".observations", "ObservationsView": ".commons", - "ObservationsViews": ".observations", "OpenAiCompletionUsageSchema": ".ingestion", "OpenAiResponseUsageSchema": ".ingestion", "OpenAiUsage": ".ingestion", @@ -535,22 +525,20 @@ "datasets": ".datasets", "health": ".health", "ingestion": ".ingestion", + "legacy": ".legacy", "llm_connections": ".llm_connections", "media": ".media", "metrics": ".metrics", - "metrics_v2": ".metrics_v2", "models": ".models", "observations": ".observations", - "observations_v2": ".observations_v2", "opentelemetry": ".opentelemetry", "organizations": ".organizations", "projects": ".projects", "prompt_version": ".prompt_version", "prompts": ".prompts", "scim": ".scim", - "score": ".score", "score_configs": ".score_configs", - "score_v2": ".score_v2", + "scores": ".scores", "sessions": ".sessions", "trace": ".trace", "utils": ".utils", @@ -640,8 +628,6 @@ def __dir__(): "CreateObservationEvent", "CreatePromptRequest", "CreateScoreConfigRequest", - "CreateScoreRequest", - "CreateScoreResponse", "CreateScoreValue", "CreateSpanBody", "CreateSpanEvent", @@ -704,7 +690,6 @@ def __dir__(): "MembershipRole", "MembershipsResponse", "MethodNotAllowedError", - "MetricsResponse", "MetricsV2Response", "Model", "ModelPrice", @@ -717,11 +702,9 @@ def __dir__(): "ObservationLevel", "ObservationType", "ObservationV2", - "Observations", "ObservationsV2Meta", "ObservationsV2Response", "ObservationsView", - "ObservationsViews", "OpenAiCompletionUsageSchema", "OpenAiResponseUsageSchema", "OpenAiUsage", @@ -827,22 +810,20 @@ def __dir__(): "datasets", "health", "ingestion", + "legacy", "llm_connections", "media", "metrics", - "metrics_v2", "models", "observations", - "observations_v2", "opentelemetry", "organizations", "projects", "prompt_version", "prompts", "scim", - "score", "score_configs", - "score_v2", + "scores", "sessions", "trace", "utils", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 041f214b9..3f656cdcd 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -25,22 +25,20 @@ from .datasets.client import AsyncDatasetsClient, DatasetsClient from .health.client import AsyncHealthClient, HealthClient from .ingestion.client import AsyncIngestionClient, IngestionClient + from .legacy.client import AsyncLegacyClient, LegacyClient from .llm_connections.client import AsyncLlmConnectionsClient, LlmConnectionsClient from .media.client import AsyncMediaClient, MediaClient from .metrics.client import AsyncMetricsClient, MetricsClient - from .metrics_v2.client import AsyncMetricsV2Client, MetricsV2Client from .models.client import AsyncModelsClient, ModelsClient from .observations.client import AsyncObservationsClient, ObservationsClient - from .observations_v2.client import AsyncObservationsV2Client, ObservationsV2Client from .opentelemetry.client import AsyncOpentelemetryClient, OpentelemetryClient from .organizations.client import AsyncOrganizationsClient, OrganizationsClient from .projects.client import AsyncProjectsClient, ProjectsClient from .prompt_version.client import AsyncPromptVersionClient, PromptVersionClient from .prompts.client import AsyncPromptsClient, PromptsClient from .scim.client import AsyncScimClient, ScimClient - from .score.client import AsyncScoreClient, ScoreClient from .score_configs.client import AsyncScoreConfigsClient, ScoreConfigsClient - from .score_v2.client import AsyncScoreV2Client, ScoreV2Client + from .scores.client import AsyncScoresClient, ScoresClient from .sessions.client import AsyncSessionsClient, SessionsClient from .trace.client import AsyncTraceClient, TraceClient @@ -133,12 +131,11 @@ def __init__( self._datasets: typing.Optional[DatasetsClient] = None self._health: typing.Optional[HealthClient] = None self._ingestion: typing.Optional[IngestionClient] = None + self._legacy: typing.Optional[LegacyClient] = None self._llm_connections: typing.Optional[LlmConnectionsClient] = None self._media: typing.Optional[MediaClient] = None - self._metrics_v2: typing.Optional[MetricsV2Client] = None self._metrics: typing.Optional[MetricsClient] = None self._models: typing.Optional[ModelsClient] = None - self._observations_v2: typing.Optional[ObservationsV2Client] = None self._observations: typing.Optional[ObservationsClient] = None self._opentelemetry: typing.Optional[OpentelemetryClient] = None self._organizations: typing.Optional[OrganizationsClient] = None @@ -147,8 +144,7 @@ def __init__( self._prompts: typing.Optional[PromptsClient] = None self._scim: typing.Optional[ScimClient] = None self._score_configs: typing.Optional[ScoreConfigsClient] = None - self._score_v2: typing.Optional[ScoreV2Client] = None - self._score: typing.Optional[ScoreClient] = None + self._scores: typing.Optional[ScoresClient] = None self._sessions: typing.Optional[SessionsClient] = None self._trace: typing.Optional[TraceClient] = None @@ -224,6 +220,14 @@ def ingestion(self): self._ingestion = IngestionClient(client_wrapper=self._client_wrapper) return self._ingestion + @property + def legacy(self): + if self._legacy is None: + from .legacy.client import LegacyClient # noqa: E402 + + self._legacy = LegacyClient(client_wrapper=self._client_wrapper) + return self._legacy + @property def llm_connections(self): if self._llm_connections is None: @@ -242,14 +246,6 @@ def media(self): self._media = MediaClient(client_wrapper=self._client_wrapper) return self._media - @property - def metrics_v2(self): - if self._metrics_v2 is None: - from .metrics_v2.client import MetricsV2Client # noqa: E402 - - self._metrics_v2 = MetricsV2Client(client_wrapper=self._client_wrapper) - return self._metrics_v2 - @property def metrics(self): if self._metrics is None: @@ -266,16 +262,6 @@ def models(self): self._models = ModelsClient(client_wrapper=self._client_wrapper) return self._models - @property - def observations_v2(self): - if self._observations_v2 is None: - from .observations_v2.client import ObservationsV2Client # noqa: E402 - - self._observations_v2 = ObservationsV2Client( - client_wrapper=self._client_wrapper - ) - return self._observations_v2 - @property def observations(self): if self._observations is None: @@ -349,20 +335,12 @@ def score_configs(self): return self._score_configs @property - def score_v2(self): - if self._score_v2 is None: - from .score_v2.client import ScoreV2Client # noqa: E402 - - self._score_v2 = ScoreV2Client(client_wrapper=self._client_wrapper) - return self._score_v2 - - @property - def score(self): - if self._score is None: - from .score.client import ScoreClient # noqa: E402 + def scores(self): + if self._scores is None: + from .scores.client import ScoresClient # noqa: E402 - self._score = ScoreClient(client_wrapper=self._client_wrapper) - return self._score + self._scores = ScoresClient(client_wrapper=self._client_wrapper) + return self._scores @property def sessions(self): @@ -469,12 +447,11 @@ def __init__( self._datasets: typing.Optional[AsyncDatasetsClient] = None self._health: typing.Optional[AsyncHealthClient] = None self._ingestion: typing.Optional[AsyncIngestionClient] = None + self._legacy: typing.Optional[AsyncLegacyClient] = None self._llm_connections: typing.Optional[AsyncLlmConnectionsClient] = None self._media: typing.Optional[AsyncMediaClient] = None - self._metrics_v2: typing.Optional[AsyncMetricsV2Client] = None self._metrics: typing.Optional[AsyncMetricsClient] = None self._models: typing.Optional[AsyncModelsClient] = None - self._observations_v2: typing.Optional[AsyncObservationsV2Client] = None self._observations: typing.Optional[AsyncObservationsClient] = None self._opentelemetry: typing.Optional[AsyncOpentelemetryClient] = None self._organizations: typing.Optional[AsyncOrganizationsClient] = None @@ -483,8 +460,7 @@ def __init__( self._prompts: typing.Optional[AsyncPromptsClient] = None self._scim: typing.Optional[AsyncScimClient] = None self._score_configs: typing.Optional[AsyncScoreConfigsClient] = None - self._score_v2: typing.Optional[AsyncScoreV2Client] = None - self._score: typing.Optional[AsyncScoreClient] = None + self._scores: typing.Optional[AsyncScoresClient] = None self._sessions: typing.Optional[AsyncSessionsClient] = None self._trace: typing.Optional[AsyncTraceClient] = None @@ -562,6 +538,14 @@ def ingestion(self): self._ingestion = AsyncIngestionClient(client_wrapper=self._client_wrapper) return self._ingestion + @property + def legacy(self): + if self._legacy is None: + from .legacy.client import AsyncLegacyClient # noqa: E402 + + self._legacy = AsyncLegacyClient(client_wrapper=self._client_wrapper) + return self._legacy + @property def llm_connections(self): if self._llm_connections is None: @@ -580,14 +564,6 @@ def media(self): self._media = AsyncMediaClient(client_wrapper=self._client_wrapper) return self._media - @property - def metrics_v2(self): - if self._metrics_v2 is None: - from .metrics_v2.client import AsyncMetricsV2Client # noqa: E402 - - self._metrics_v2 = AsyncMetricsV2Client(client_wrapper=self._client_wrapper) - return self._metrics_v2 - @property def metrics(self): if self._metrics is None: @@ -604,16 +580,6 @@ def models(self): self._models = AsyncModelsClient(client_wrapper=self._client_wrapper) return self._models - @property - def observations_v2(self): - if self._observations_v2 is None: - from .observations_v2.client import AsyncObservationsV2Client # noqa: E402 - - self._observations_v2 = AsyncObservationsV2Client( - client_wrapper=self._client_wrapper - ) - return self._observations_v2 - @property def observations(self): if self._observations is None: @@ -689,20 +655,12 @@ def score_configs(self): return self._score_configs @property - def score_v2(self): - if self._score_v2 is None: - from .score_v2.client import AsyncScoreV2Client # noqa: E402 - - self._score_v2 = AsyncScoreV2Client(client_wrapper=self._client_wrapper) - return self._score_v2 - - @property - def score(self): - if self._score is None: - from .score.client import AsyncScoreClient # noqa: E402 + def scores(self): + if self._scores is None: + from .scores.client import AsyncScoresClient # noqa: E402 - self._score = AsyncScoreClient(client_wrapper=self._client_wrapper) - return self._score + self._scores = AsyncScoresClient(client_wrapper=self._client_wrapper) + return self._scores @property def sessions(self): diff --git a/langfuse/api/legacy/__init__.py b/langfuse/api/legacy/__init__.py new file mode 100644 index 000000000..d91b42c2b --- /dev/null +++ b/langfuse/api/legacy/__init__.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import metrics_v1, observations_v1, score_v1 + from .metrics_v1 import MetricsResponse + from .observations_v1 import Observations, ObservationsViews + from .score_v1 import CreateScoreRequest, CreateScoreResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateScoreRequest": ".score_v1", + "CreateScoreResponse": ".score_v1", + "MetricsResponse": ".metrics_v1", + "Observations": ".observations_v1", + "ObservationsViews": ".observations_v1", + "metrics_v1": ".metrics_v1", + "observations_v1": ".observations_v1", + "score_v1": ".score_v1", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateScoreRequest", + "CreateScoreResponse", + "MetricsResponse", + "Observations", + "ObservationsViews", + "metrics_v1", + "observations_v1", + "score_v1", +] diff --git a/langfuse/api/legacy/client.py b/langfuse/api/legacy/client.py new file mode 100644 index 000000000..3886b9eac --- /dev/null +++ b/langfuse/api/legacy/client.py @@ -0,0 +1,105 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawLegacyClient, RawLegacyClient + +if typing.TYPE_CHECKING: + from .metrics_v1.client import AsyncMetricsV1Client, MetricsV1Client + from .observations_v1.client import AsyncObservationsV1Client, ObservationsV1Client + from .score_v1.client import AsyncScoreV1Client, ScoreV1Client + + +class LegacyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawLegacyClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._metrics_v1: typing.Optional[MetricsV1Client] = None + self._observations_v1: typing.Optional[ObservationsV1Client] = None + self._score_v1: typing.Optional[ScoreV1Client] = None + + @property + def with_raw_response(self) -> RawLegacyClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawLegacyClient + """ + return self._raw_client + + @property + def metrics_v1(self): + if self._metrics_v1 is None: + from .metrics_v1.client import MetricsV1Client # noqa: E402 + + self._metrics_v1 = MetricsV1Client(client_wrapper=self._client_wrapper) + return self._metrics_v1 + + @property + def observations_v1(self): + if self._observations_v1 is None: + from .observations_v1.client import ObservationsV1Client # noqa: E402 + + self._observations_v1 = ObservationsV1Client( + client_wrapper=self._client_wrapper + ) + return self._observations_v1 + + @property + def score_v1(self): + if self._score_v1 is None: + from .score_v1.client import ScoreV1Client # noqa: E402 + + self._score_v1 = ScoreV1Client(client_wrapper=self._client_wrapper) + return self._score_v1 + + +class AsyncLegacyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawLegacyClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._metrics_v1: typing.Optional[AsyncMetricsV1Client] = None + self._observations_v1: typing.Optional[AsyncObservationsV1Client] = None + self._score_v1: typing.Optional[AsyncScoreV1Client] = None + + @property + def with_raw_response(self) -> AsyncRawLegacyClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawLegacyClient + """ + return self._raw_client + + @property + def metrics_v1(self): + if self._metrics_v1 is None: + from .metrics_v1.client import AsyncMetricsV1Client # noqa: E402 + + self._metrics_v1 = AsyncMetricsV1Client(client_wrapper=self._client_wrapper) + return self._metrics_v1 + + @property + def observations_v1(self): + if self._observations_v1 is None: + from .observations_v1.client import AsyncObservationsV1Client # noqa: E402 + + self._observations_v1 = AsyncObservationsV1Client( + client_wrapper=self._client_wrapper + ) + return self._observations_v1 + + @property + def score_v1(self): + if self._score_v1 is None: + from .score_v1.client import AsyncScoreV1Client # noqa: E402 + + self._score_v1 = AsyncScoreV1Client(client_wrapper=self._client_wrapper) + return self._score_v1 diff --git a/langfuse/api/metrics_v2/__init__.py b/langfuse/api/legacy/metrics_v1/__init__.py similarity index 87% rename from langfuse/api/metrics_v2/__init__.py rename to langfuse/api/legacy/metrics_v1/__init__.py index 0421785de..fb47bc976 100644 --- a/langfuse/api/metrics_v2/__init__.py +++ b/langfuse/api/legacy/metrics_v1/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import MetricsV2Response -_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".types"} + from .types import MetricsResponse +_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsV2Response"] +__all__ = ["MetricsResponse"] diff --git a/langfuse/api/legacy/metrics_v1/client.py b/langfuse/api/legacy/metrics_v1/client.py new file mode 100644 index 000000000..bece982b3 --- /dev/null +++ b/langfuse/api/legacy/metrics_v1/client.py @@ -0,0 +1,214 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawMetricsV1Client, RawMetricsV1Client +from .types.metrics_response import MetricsResponse + + +class MetricsV1Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMetricsV1Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMetricsV1Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMetricsV1Client + """ + return self._raw_client + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> MetricsResponse: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MetricsResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.legacy.metrics_v1.metrics( + query="query", + ) + """ + _response = self._raw_client.metrics( + query=query, request_options=request_options + ) + return _response.data + + +class AsyncMetricsV1Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMetricsV1Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMetricsV1Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMetricsV1Client + """ + return self._raw_client + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> MetricsResponse: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + MetricsResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.legacy.metrics_v1.metrics( + query="query", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.metrics( + query=query, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/legacy/metrics_v1/raw_client.py b/langfuse/api/legacy/metrics_v1/raw_client.py new file mode 100644 index 000000000..61f03e541 --- /dev/null +++ b/langfuse/api/legacy/metrics_v1/raw_client.py @@ -0,0 +1,322 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...commons.errors.access_denied_error import AccessDeniedError +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import MethodNotAllowedError +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import UnauthorizedError +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from .types.metrics_response import MetricsResponse + + +class RawMetricsV1Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[MetricsResponse]: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[MetricsResponse] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsResponse, + parse_obj_as( + type_=MetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawMetricsV1Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def metrics( + self, *, query: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[MetricsResponse]: + """ + Get metrics from the Langfuse project using a query object. + + Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + + For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + Parameters + ---------- + query : str + JSON string containing the query parameters with the following structure: + ```json + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + ``` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[MetricsResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/metrics", + method="GET", + params={ + "query": query, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + MetricsResponse, + parse_obj_as( + type_=MetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/metrics_v2/types/__init__.py b/langfuse/api/legacy/metrics_v1/types/__init__.py similarity index 85% rename from langfuse/api/metrics_v2/types/__init__.py rename to langfuse/api/legacy/metrics_v1/types/__init__.py index b9510d24f..308847504 100644 --- a/langfuse/api/metrics_v2/types/__init__.py +++ b/langfuse/api/legacy/metrics_v1/types/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .metrics_v2response import MetricsV2Response -_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".metrics_v2response"} + from .metrics_response import MetricsResponse +_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".metrics_response"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsV2Response"] +__all__ = ["MetricsResponse"] diff --git a/langfuse/api/metrics/types/metrics_response.py b/langfuse/api/legacy/metrics_v1/types/metrics_response.py similarity index 90% rename from langfuse/api/metrics/types/metrics_response.py rename to langfuse/api/legacy/metrics_v1/types/metrics_response.py index ffbbfaa59..734c47ffd 100644 --- a/langfuse/api/metrics/types/metrics_response.py +++ b/langfuse/api/legacy/metrics_v1/types/metrics_response.py @@ -3,7 +3,7 @@ import typing import pydantic -from ...core.pydantic_utilities import UniversalBaseModel +from ....core.pydantic_utilities import UniversalBaseModel class MetricsResponse(UniversalBaseModel): diff --git a/langfuse/api/observations_v2/__init__.py b/langfuse/api/legacy/observations_v1/__init__.py similarity index 83% rename from langfuse/api/observations_v2/__init__.py rename to langfuse/api/legacy/observations_v1/__init__.py index 66816e540..22b445984 100644 --- a/langfuse/api/observations_v2/__init__.py +++ b/langfuse/api/legacy/observations_v1/__init__.py @@ -6,10 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ObservationsV2Meta, ObservationsV2Response + from .types import Observations, ObservationsViews _dynamic_imports: typing.Dict[str, str] = { - "ObservationsV2Meta": ".types", - "ObservationsV2Response": ".types", + "Observations": ".types", + "ObservationsViews": ".types", } @@ -40,4 +40,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] +__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/observations_v2/client.py b/langfuse/api/legacy/observations_v1/client.py similarity index 66% rename from langfuse/api/observations_v2/client.py rename to langfuse/api/legacy/observations_v1/client.py index 923a4db4b..40d5df6b8 100644 --- a/langfuse/api/observations_v2/client.py +++ b/langfuse/api/legacy/observations_v1/client.py @@ -3,36 +3,76 @@ import datetime as dt import typing -from ..commons.types.observation_level import ObservationLevel -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawObservationsV2Client, RawObservationsV2Client -from .types.observations_v2response import ObservationsV2Response +from ...commons.types.observation_level import ObservationLevel +from ...commons.types.observations_view import ObservationsView +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawObservationsV1Client, RawObservationsV1Client +from .types.observations_views import ObservationsViews -class ObservationsV2Client: +class ObservationsV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawObservationsV2Client(client_wrapper=client_wrapper) + self._raw_client = RawObservationsV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawObservationsV2Client: + def with_raw_response(self) -> RawObservationsV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawObservationsV2Client + RawObservationsV1Client """ return self._raw_client + def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsView: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsView + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.legacy.observations_v1.get( + observation_id="observationId", + ) + """ + _response = self._raw_client.get( + observation_id, request_options=request_options + ) + return _response.data + def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -45,64 +85,25 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsV2Response: + ) -> ObservationsViews: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -163,13 +164,6 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -187,13 +181,19 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -224,7 +224,7 @@ def get_many( Returns ------- - ObservationsV2Response + ObservationsViews Examples -------- @@ -238,14 +238,11 @@ def get_many( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.observations_v2.get_many() + client.legacy.observations_v1.get_many() """ _response = self._raw_client.get_many( - fields=fields, - expand_metadata=expand_metadata, + page=page, limit=limit, - cursor=cursor, - parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, @@ -262,29 +259,76 @@ def get_many( return _response.data -class AsyncObservationsV2Client: +class AsyncObservationsV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawObservationsV2Client(client_wrapper=client_wrapper) + self._raw_client = AsyncRawObservationsV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawObservationsV2Client: + def with_raw_response(self) -> AsyncRawObservationsV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawObservationsV2Client + AsyncRawObservationsV1Client """ return self._raw_client + async def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> ObservationsView: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ObservationsView + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.legacy.observations_v1.get( + observation_id="observationId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + observation_id, request_options=request_options + ) + return _response.data + async def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -297,64 +341,25 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsV2Response: + ) -> ObservationsViews: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -415,13 +420,6 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -439,13 +437,19 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -476,7 +480,7 @@ async def get_many( Returns ------- - ObservationsV2Response + ObservationsViews Examples -------- @@ -495,17 +499,14 @@ async def get_many( async def main() -> None: - await client.observations_v2.get_many() + await client.legacy.observations_v1.get_many() asyncio.run(main()) """ _response = await self._raw_client.get_many( - fields=fields, - expand_metadata=expand_metadata, + page=page, limit=limit, - cursor=cursor, - parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, diff --git a/langfuse/api/observations_v2/raw_client.py b/langfuse/api/legacy/observations_v1/raw_client.py similarity index 65% rename from langfuse/api/observations_v2/raw_client.py rename to langfuse/api/legacy/observations_v1/raw_client.py index f65a45502..61ecf409d 100644 --- a/langfuse/api/observations_v2/raw_client.py +++ b/langfuse/api/legacy/observations_v1/raw_client.py @@ -4,33 +4,136 @@ import typing from json.decoder import JSONDecodeError -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.observation_level import ObservationLevel -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.datetime_utils import serialize_datetime -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from .types.observations_v2response import ObservationsV2Response - - -class RawObservationsV2Client: +from ...commons.errors.access_denied_error import AccessDeniedError +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import MethodNotAllowedError +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import UnauthorizedError +from ...commons.types.observation_level import ObservationLevel +from ...commons.types.observations_view import ObservationsView +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.datetime_utils import serialize_datetime +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from .types.observations_views import ObservationsViews + + +class RawObservationsV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper + def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ObservationsView]: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ObservationsView] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/observations/{jsonable_encoder(observation_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsView, + parse_obj_as( + type_=ObservationsView, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -43,64 +146,25 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ObservationsV2Response]: + ) -> HttpResponse[ObservationsViews]: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -161,13 +225,6 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -185,13 +242,19 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -222,17 +285,14 @@ def get_many( Returns ------- - HttpResponse[ObservationsV2Response] + HttpResponse[ObservationsViews] """ _response = self._client_wrapper.httpx_client.request( - "api/public/v2/observations", + "api/public/observations", method="GET", params={ - "fields": fields, - "expandMetadata": expand_metadata, + "page": page, "limit": limit, - "cursor": cursor, - "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -254,9 +314,9 @@ def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsV2Response, + ObservationsViews, parse_obj_as( - type_=ObservationsV2Response, # type: ignore + type_=ObservationsViews, # type: ignore object_=_response.json(), ), ) @@ -330,18 +390,119 @@ def get_many( ) -class AsyncRawObservationsV2Client: +class AsyncRawObservationsV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper + async def get( + self, + observation_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ObservationsView]: + """ + Get a observation + + Parameters + ---------- + observation_id : str + The unique langfuse identifier of an observation, can be an event, span or generation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ObservationsView] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/observations/{jsonable_encoder(observation_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ObservationsView, + parse_obj_as( + type_=ObservationsView, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + async def get_many( self, *, - fields: typing.Optional[str] = None, - expand_metadata: typing.Optional[str] = None, + page: typing.Optional[int] = None, limit: typing.Optional[int] = None, - cursor: typing.Optional[str] = None, - parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -354,64 +515,25 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ObservationsV2Response]: + ) -> AsyncHttpResponse[ObservationsViews]: """ - Get a list of observations with cursor-based pagination and flexible field selection. - - ## Cursor-based Pagination - This endpoint uses cursor-based pagination for efficient traversal of large datasets. - The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. - - ## Field Selection - Use the `fields` parameter to control which observation fields are returned: - - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type - - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId - - `time` - completionStartTime, createdAt, updatedAt - - `io` - input, output - - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost - - `prompt` - promptId, promptName, promptVersion - - `metrics` - latency, timeToFirstToken - - If not specified, `core` and `basic` field groups are returned. - - ## Filters - Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + Get a list of observations. + + Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. Parameters ---------- - fields : typing.Optional[str] - Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. - If not specified, `core` and `basic` field groups are returned. - Example: "basic,usage,model" - - expand_metadata : typing.Optional[str] - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" + page : typing.Optional[int] + Page number, starts at 1. limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. - - cursor : typing.Optional[str] - Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. - - parse_io_as_json : typing.Optional[bool] - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. + Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -472,13 +594,6 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag - - `userId` (string) - User ID - - `sessionId` (string) - Session ID - - ### Trace-Related Fields - - `traceName` (string) - Name of the parent trace - - `traceTags` (arrayOptions) - Tags from the parent trace - - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -496,13 +611,19 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name (alias: `providedModelName`) + - `model` (string) - Provided model name - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + ### Associated Trace Fields (requires join with traces table) + - `userId` (string) - User ID from associated trace + - `traceName` (string) - Name from associated trace + - `traceEnvironment` (string) - Environment from associated trace + - `traceTags` (arrayOptions) - Tags from associated trace + ## Filter Examples ```json [ @@ -533,17 +654,14 @@ async def get_many( Returns ------- - AsyncHttpResponse[ObservationsV2Response] + AsyncHttpResponse[ObservationsViews] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/observations", + "api/public/observations", method="GET", params={ - "fields": fields, - "expandMetadata": expand_metadata, + "page": page, "limit": limit, - "cursor": cursor, - "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -565,9 +683,9 @@ async def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsV2Response, + ObservationsViews, parse_obj_as( - type_=ObservationsV2Response, # type: ignore + type_=ObservationsViews, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/observations_v2/types/__init__.py b/langfuse/api/legacy/observations_v1/types/__init__.py similarity index 78% rename from langfuse/api/observations_v2/types/__init__.py rename to langfuse/api/legacy/observations_v1/types/__init__.py index 6e132aba6..247b674a1 100644 --- a/langfuse/api/observations_v2/types/__init__.py +++ b/langfuse/api/legacy/observations_v1/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .observations_v2meta import ObservationsV2Meta - from .observations_v2response import ObservationsV2Response + from .observations import Observations + from .observations_views import ObservationsViews _dynamic_imports: typing.Dict[str, str] = { - "ObservationsV2Meta": ".observations_v2meta", - "ObservationsV2Response": ".observations_v2response", + "Observations": ".observations", + "ObservationsViews": ".observations_views", } @@ -41,4 +41,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] +__all__ = ["Observations", "ObservationsViews"] diff --git a/langfuse/api/observations/types/observations.py b/langfuse/api/legacy/observations_v1/types/observations.py similarity index 63% rename from langfuse/api/observations/types/observations.py rename to langfuse/api/legacy/observations_v1/types/observations.py index 61a47cdc5..860047ef2 100644 --- a/langfuse/api/observations/types/observations.py +++ b/langfuse/api/legacy/observations_v1/types/observations.py @@ -3,9 +3,9 @@ import typing import pydantic -from ...commons.types.observation import Observation -from ...core.pydantic_utilities import UniversalBaseModel -from ...utils.pagination.types.meta_response import MetaResponse +from ....commons.types.observation import Observation +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse class Observations(UniversalBaseModel): diff --git a/langfuse/api/observations/types/observations_views.py b/langfuse/api/legacy/observations_v1/types/observations_views.py similarity index 63% rename from langfuse/api/observations/types/observations_views.py rename to langfuse/api/legacy/observations_v1/types/observations_views.py index ee682ed39..b6d294c7d 100644 --- a/langfuse/api/observations/types/observations_views.py +++ b/langfuse/api/legacy/observations_v1/types/observations_views.py @@ -3,9 +3,9 @@ import typing import pydantic -from ...commons.types.observations_view import ObservationsView -from ...core.pydantic_utilities import UniversalBaseModel -from ...utils.pagination.types.meta_response import MetaResponse +from ....commons.types.observations_view import ObservationsView +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse class ObservationsViews(UniversalBaseModel): diff --git a/langfuse/api/legacy/raw_client.py b/langfuse/api/legacy/raw_client.py new file mode 100644 index 000000000..0672ed01d --- /dev/null +++ b/langfuse/api/legacy/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawLegacyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawLegacyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/langfuse/api/score/__init__.py b/langfuse/api/legacy/score_v1/__init__.py similarity index 100% rename from langfuse/api/score/__init__.py rename to langfuse/api/legacy/score_v1/__init__.py diff --git a/langfuse/api/score/client.py b/langfuse/api/legacy/score_v1/client.py similarity index 92% rename from langfuse/api/score/client.py rename to langfuse/api/legacy/score_v1/client.py index 7a2ba1b83..7c7a214e3 100644 --- a/langfuse/api/score/client.py +++ b/langfuse/api/legacy/score_v1/client.py @@ -2,29 +2,29 @@ import typing -from ..commons.types.create_score_value import CreateScoreValue -from ..commons.types.score_data_type import ScoreDataType -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawScoreClient, RawScoreClient +from ...commons.types.create_score_value import CreateScoreValue +from ...commons.types.score_data_type import ScoreDataType +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawScoreV1Client, RawScoreV1Client from .types.create_score_response import CreateScoreResponse # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) -class ScoreClient: +class ScoreV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawScoreClient(client_wrapper=client_wrapper) + self._raw_client = RawScoreV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawScoreClient: + def with_raw_response(self) -> RawScoreV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawScoreClient + RawScoreV1Client """ return self._raw_client @@ -101,7 +101,7 @@ def create( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score.create( + client.legacy.score_v1.create( name="name", value=1.1, ) @@ -154,7 +154,7 @@ def delete( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score.delete( + client.legacy.score_v1.delete( score_id="scoreId", ) """ @@ -162,18 +162,18 @@ def delete( return _response.data -class AsyncScoreClient: +class AsyncScoreV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawScoreClient(client_wrapper=client_wrapper) + self._raw_client = AsyncRawScoreV1Client(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawScoreClient: + def with_raw_response(self) -> AsyncRawScoreV1Client: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawScoreClient + AsyncRawScoreV1Client """ return self._raw_client @@ -255,7 +255,7 @@ async def create( async def main() -> None: - await client.score.create( + await client.legacy.score_v1.create( name="name", value=1.1, ) @@ -316,7 +316,7 @@ async def delete( async def main() -> None: - await client.score.delete( + await client.legacy.score_v1.delete( score_id="scoreId", ) diff --git a/langfuse/api/score/raw_client.py b/langfuse/api/legacy/score_v1/raw_client.py similarity index 95% rename from langfuse/api/score/raw_client.py rename to langfuse/api/legacy/score_v1/raw_client.py index 7c20eee08..9bcbe082d 100644 --- a/langfuse/api/score/raw_client.py +++ b/langfuse/api/legacy/score_v1/raw_client.py @@ -3,27 +3,27 @@ import typing from json.decoder import JSONDecodeError -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..commons.types.create_score_value import CreateScoreValue -from ..commons.types.score_data_type import ScoreDataType -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.jsonable_encoder import jsonable_encoder -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from ..core.serialization import convert_and_respect_annotation_metadata +from ...commons.errors.access_denied_error import AccessDeniedError +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import MethodNotAllowedError +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import UnauthorizedError +from ...commons.types.create_score_value import CreateScoreValue +from ...commons.types.score_data_type import ScoreDataType +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata from .types.create_score_response import CreateScoreResponse # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) -class RawScoreClient: +class RawScoreV1Client: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper @@ -284,7 +284,7 @@ def delete( ) -class AsyncRawScoreClient: +class AsyncRawScoreV1Client: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper diff --git a/langfuse/api/score/types/__init__.py b/langfuse/api/legacy/score_v1/types/__init__.py similarity index 100% rename from langfuse/api/score/types/__init__.py rename to langfuse/api/legacy/score_v1/types/__init__.py diff --git a/langfuse/api/score/types/create_score_request.py b/langfuse/api/legacy/score_v1/types/create_score_request.py similarity index 89% rename from langfuse/api/score/types/create_score_request.py rename to langfuse/api/legacy/score_v1/types/create_score_request.py index 5491031ca..d54333ac3 100644 --- a/langfuse/api/score/types/create_score_request.py +++ b/langfuse/api/legacy/score_v1/types/create_score_request.py @@ -4,17 +4,17 @@ import pydantic import typing_extensions -from ...commons.types.create_score_value import CreateScoreValue -from ...commons.types.score_data_type import ScoreDataType -from ...core.pydantic_utilities import UniversalBaseModel -from ...core.serialization import FieldMetadata +from ....commons.types.create_score_value import CreateScoreValue +from ....commons.types.score_data_type import ScoreDataType +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata class CreateScoreRequest(UniversalBaseModel): """ Examples -------- - from langfuse.score import CreateScoreRequest + from langfuse.legacy.score_v1 import CreateScoreRequest CreateScoreRequest( name="novelty", diff --git a/langfuse/api/score/types/create_score_response.py b/langfuse/api/legacy/score_v1/types/create_score_response.py similarity index 85% rename from langfuse/api/score/types/create_score_response.py rename to langfuse/api/legacy/score_v1/types/create_score_response.py index 1c20c0f3a..ff1d27c2c 100644 --- a/langfuse/api/score/types/create_score_response.py +++ b/langfuse/api/legacy/score_v1/types/create_score_response.py @@ -3,7 +3,7 @@ import typing import pydantic -from ...core.pydantic_utilities import UniversalBaseModel +from ....core.pydantic_utilities import UniversalBaseModel class CreateScoreResponse(UniversalBaseModel): diff --git a/langfuse/api/metrics/__init__.py b/langfuse/api/metrics/__init__.py index fb47bc976..0421785de 100644 --- a/langfuse/api/metrics/__init__.py +++ b/langfuse/api/metrics/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import MetricsResponse -_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".types"} + from .types import MetricsV2Response +_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsResponse"] +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/metrics/client.py b/langfuse/api/metrics/client.py index a7a3fe359..ed584d99b 100644 --- a/langfuse/api/metrics/client.py +++ b/langfuse/api/metrics/client.py @@ -5,7 +5,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawMetricsClient, RawMetricsClient -from .types.metrics_response import MetricsResponse +from .types.metrics_v2response import MetricsV2Response class MetricsClient: @@ -25,55 +25,159 @@ def with_raw_response(self) -> RawMetricsClient: def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsResponse: + ) -> MetricsV2Response: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -83,7 +187,7 @@ def metrics( Returns ------- - MetricsResponse + MetricsV2Response Examples -------- @@ -124,55 +228,159 @@ def with_raw_response(self) -> AsyncRawMetricsClient: async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsResponse: + ) -> MetricsV2Response: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -182,7 +390,7 @@ async def metrics( Returns ------- - MetricsResponse + MetricsV2Response Examples -------- diff --git a/langfuse/api/metrics/raw_client.py b/langfuse/api/metrics/raw_client.py index 605b095db..69c976bcc 100644 --- a/langfuse/api/metrics/raw_client.py +++ b/langfuse/api/metrics/raw_client.py @@ -13,7 +13,7 @@ from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions -from .types.metrics_response import MetricsResponse +from .types.metrics_v2response import MetricsV2Response class RawMetricsClient: @@ -22,55 +22,159 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[MetricsResponse]: + ) -> HttpResponse[MetricsV2Response]: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -80,10 +184,10 @@ def metrics( Returns ------- - HttpResponse[MetricsResponse] + HttpResponse[MetricsV2Response] """ _response = self._client_wrapper.httpx_client.request( - "api/public/metrics", + "api/public/v2/metrics", method="GET", params={ "query": query, @@ -93,9 +197,9 @@ def metrics( try: if 200 <= _response.status_code < 300: _data = typing.cast( - MetricsResponse, + MetricsV2Response, parse_obj_as( - type_=MetricsResponse, # type: ignore + type_=MetricsV2Response, # type: ignore object_=_response.json(), ), ) @@ -175,55 +279,159 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def metrics( self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[MetricsResponse]: + ) -> AsyncHttpResponse[MetricsV2Response]: """ - Get metrics from the Langfuse project using a query object. + Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - Consider using the [v2 metrics endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for better performance. + ## V2 Differences + - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) + - Direct access to tags and release fields on observations + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view + - High cardinality dimensions are not supported and will return a 400 error (see below) For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). + ## Available Views + + ### observations + Query observation-level data (spans, generations, events). + + **Dimensions:** + - `environment` - Deployment environment (e.g., production, staging) + - `type` - Type of observation (SPAN, GENERATION, EVENT) + - `name` - Name of the observation + - `level` - Logging level of the observation + - `version` - Version of the observation + - `tags` - User-defined tags + - `release` - Release version + - `traceName` - Name of the parent trace (backwards-compatible) + - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) + - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) + - `providedModelName` - Name of the model used + - `promptName` - Name of the prompt used + - `promptVersion` - Version of the prompt used + - `startTimeMonth` - Month of start_time in YYYY-MM format + + **Measures:** + - `count` - Total number of observations + - `latency` - Observation latency (milliseconds) + - `streamingLatency` - Generation latency from completion start to end (milliseconds) + - `inputTokens` - Sum of input tokens consumed + - `outputTokens` - Sum of output tokens produced + - `totalTokens` - Sum of all tokens consumed + - `outputTokensPerSecond` - Output tokens per second + - `tokensPerSecond` - Total tokens per second + - `inputCost` - Input cost (USD) + - `outputCost` - Output cost (USD) + - `totalCost` - Total cost (USD) + - `timeToFirstToken` - Time to first token (milliseconds) + - `countScores` - Number of scores attached to the observation + + ### scores-numeric + Query numeric and boolean score data. + + **Dimensions:** + - `environment` - Deployment environment + - `name` - Name of the score (e.g., accuracy, toxicity) + - `source` - Origin of the score (API, ANNOTATION, EVAL) + - `dataType` - Data type (NUMERIC, BOOLEAN) + - `configId` - Identifier of the score config + - `timestampMonth` - Month in YYYY-MM format + - `timestampDay` - Day in YYYY-MM-DD format + - `value` - Numeric value of the score + - `traceName` - Name of the parent trace + - `tags` - Tags + - `traceRelease` - Release version + - `traceVersion` - Version + - `observationName` - Name of the associated observation + - `observationModelName` - Model name of the associated observation + - `observationPromptName` - Prompt name of the associated observation + - `observationPromptVersion` - Prompt version of the associated observation + + **Measures:** + - `count` - Total number of scores + - `value` - Score value (for aggregations) + + ### scores-categorical + Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. + + **Measures:** + - `count` - Total number of scores + + ## High Cardinality Dimensions + The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. + Use them in filters instead. + + **observations view:** + - `id` - Use traceId filter to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `parentObservationId` - Use parentObservationId filter instead + + **scores-numeric / scores-categorical views:** + - `id` - Use specific filters to narrow down results + - `traceId` - Use traceId filter instead + - `userId` - Use userId filter instead + - `sessionId` - Use sessionId filter instead + - `observationId` - Use observationId filter instead + + ## Aggregations + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + ## Time Granularities + Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` + - `auto` bins the data into approximately 50 buckets based on the time range + Parameters ---------- query : str JSON string containing the query parameters with the following structure: ```json { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" + "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" "dimensions": [ // Optional. Default: [] { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" + "field": string // Field to group by (see available dimensions above) } ], "metrics": [ // Required. At least one metric must be provided { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" } ], "filters": [ // Optional. Default: [] { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) } ], "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" }, "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) "orderBy": [ // Optional. Default: null { - "field": string, // Field to order by + "field": string, // Field to order by (dimension or metric alias) "direction": string // "asc" or "desc" } ], "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 } } ``` @@ -233,10 +441,10 @@ async def metrics( Returns ------- - AsyncHttpResponse[MetricsResponse] + AsyncHttpResponse[MetricsV2Response] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/metrics", + "api/public/v2/metrics", method="GET", params={ "query": query, @@ -246,9 +454,9 @@ async def metrics( try: if 200 <= _response.status_code < 300: _data = typing.cast( - MetricsResponse, + MetricsV2Response, parse_obj_as( - type_=MetricsResponse, # type: ignore + type_=MetricsV2Response, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/metrics/types/__init__.py b/langfuse/api/metrics/types/__init__.py index 308847504..b9510d24f 100644 --- a/langfuse/api/metrics/types/__init__.py +++ b/langfuse/api/metrics/types/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .metrics_response import MetricsResponse -_dynamic_imports: typing.Dict[str, str] = {"MetricsResponse": ".metrics_response"} + from .metrics_v2response import MetricsV2Response +_dynamic_imports: typing.Dict[str, str] = {"MetricsV2Response": ".metrics_v2response"} def __getattr__(attr_name: str) -> typing.Any: @@ -37,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["MetricsResponse"] +__all__ = ["MetricsV2Response"] diff --git a/langfuse/api/metrics_v2/types/metrics_v2response.py b/langfuse/api/metrics/types/metrics_v2response.py similarity index 100% rename from langfuse/api/metrics_v2/types/metrics_v2response.py rename to langfuse/api/metrics/types/metrics_v2response.py diff --git a/langfuse/api/metrics_v2/client.py b/langfuse/api/metrics_v2/client.py deleted file mode 100644 index d6c05914c..000000000 --- a/langfuse/api/metrics_v2/client.py +++ /dev/null @@ -1,422 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawMetricsV2Client, RawMetricsV2Client -from .types.metrics_v2response import MetricsV2Response - - -class MetricsV2Client: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawMetricsV2Client(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> RawMetricsV2Client: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawMetricsV2Client - """ - return self._raw_client - - def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsV2Response: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MetricsV2Response - - Examples - -------- - from langfuse import LangfuseAPI - - client = LangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.metrics_v2.metrics( - query="query", - ) - """ - _response = self._raw_client.metrics( - query=query, request_options=request_options - ) - return _response.data - - -class AsyncMetricsV2Client: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawMetricsV2Client(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawMetricsV2Client: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawMetricsV2Client - """ - return self._raw_client - - async def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> MetricsV2Response: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - MetricsV2Response - - Examples - -------- - import asyncio - - from langfuse import AsyncLangfuseAPI - - client = AsyncLangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.metrics_v2.metrics( - query="query", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.metrics( - query=query, request_options=request_options - ) - return _response.data diff --git a/langfuse/api/metrics_v2/raw_client.py b/langfuse/api/metrics_v2/raw_client.py deleted file mode 100644 index b79d55713..000000000 --- a/langfuse/api/metrics_v2/raw_client.py +++ /dev/null @@ -1,530 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ..commons.errors.access_denied_error import AccessDeniedError -from ..commons.errors.error import Error -from ..commons.errors.method_not_allowed_error import MethodNotAllowedError -from ..commons.errors.not_found_error import NotFoundError -from ..commons.errors.unauthorized_error import UnauthorizedError -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from .types.metrics_v2response import MetricsV2Response - - -class RawMetricsV2Client: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[MetricsV2Response]: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[MetricsV2Response] - """ - _response = self._client_wrapper.httpx_client.request( - "api/public/v2/metrics", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - MetricsV2Response, - parse_obj_as( - type_=MetricsV2Response, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) - - -class AsyncRawMetricsV2Client: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def metrics( - self, *, query: str, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[MetricsV2Response]: - """ - Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance. - - ## V2 Differences - - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported) - - Direct access to tags and release fields on observations - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view - - High cardinality dimensions are not supported and will return a 400 error (see below) - - For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - ## Available Views - - ### observations - Query observation-level data (spans, generations, events). - - **Dimensions:** - - `environment` - Deployment environment (e.g., production, staging) - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - `name` - Name of the observation - - `level` - Logging level of the observation - - `version` - Version of the observation - - `tags` - User-defined tags - - `release` - Release version - - `traceName` - Name of the parent trace (backwards-compatible) - - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release) - - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version) - - `providedModelName` - Name of the model used - - `promptName` - Name of the prompt used - - `promptVersion` - Version of the prompt used - - `startTimeMonth` - Month of start_time in YYYY-MM format - - **Measures:** - - `count` - Total number of observations - - `latency` - Observation latency (milliseconds) - - `streamingLatency` - Generation latency from completion start to end (milliseconds) - - `inputTokens` - Sum of input tokens consumed - - `outputTokens` - Sum of output tokens produced - - `totalTokens` - Sum of all tokens consumed - - `outputTokensPerSecond` - Output tokens per second - - `tokensPerSecond` - Total tokens per second - - `inputCost` - Input cost (USD) - - `outputCost` - Output cost (USD) - - `totalCost` - Total cost (USD) - - `timeToFirstToken` - Time to first token (milliseconds) - - `countScores` - Number of scores attached to the observation - - ### scores-numeric - Query numeric and boolean score data. - - **Dimensions:** - - `environment` - Deployment environment - - `name` - Name of the score (e.g., accuracy, toxicity) - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - `dataType` - Data type (NUMERIC, BOOLEAN) - - `configId` - Identifier of the score config - - `timestampMonth` - Month in YYYY-MM format - - `timestampDay` - Day in YYYY-MM-DD format - - `value` - Numeric value of the score - - `traceName` - Name of the parent trace - - `tags` - Tags - - `traceRelease` - Release version - - `traceVersion` - Version - - `observationName` - Name of the associated observation - - `observationModelName` - Model name of the associated observation - - `observationPromptName` - Prompt name of the associated observation - - `observationPromptVersion` - Prompt version of the associated observation - - **Measures:** - - `count` - Total number of scores - - `value` - Score value (for aggregations) - - ### scores-categorical - Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`. - - **Measures:** - - `count` - Total number of scores - - ## High Cardinality Dimensions - The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues. - Use them in filters instead. - - **observations view:** - - `id` - Use traceId filter to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `parentObservationId` - Use parentObservationId filter instead - - **scores-numeric / scores-categorical views:** - - `id` - Use specific filters to narrow down results - - `traceId` - Use traceId filter instead - - `userId` - Use userId filter instead - - `sessionId` - Use sessionId filter instead - - `observationId` - Use observationId filter instead - - ## Aggregations - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - ## Time Granularities - Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month` - - `auto` bins the data into approximately 50 buckets based on the time range - - Parameters - ---------- - query : str - JSON string containing the query parameters with the following structure: - ```json - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - ``` - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[MetricsV2Response] - """ - _response = await self._client_wrapper.httpx_client.request( - "api/public/v2/metrics", - method="GET", - params={ - "query": query, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - MetricsV2Response, - parse_obj_as( - type_=MetricsV2Response, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) diff --git a/langfuse/api/observations/__init__.py b/langfuse/api/observations/__init__.py index 22b445984..66816e540 100644 --- a/langfuse/api/observations/__init__.py +++ b/langfuse/api/observations/__init__.py @@ -6,10 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import Observations, ObservationsViews + from .types import ObservationsV2Meta, ObservationsV2Response _dynamic_imports: typing.Dict[str, str] = { - "Observations": ".types", - "ObservationsViews": ".types", + "ObservationsV2Meta": ".types", + "ObservationsV2Response": ".types", } @@ -40,4 +40,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["Observations", "ObservationsViews"] +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/observations/client.py b/langfuse/api/observations/client.py index 6be2a71f4..ce0de0cf2 100644 --- a/langfuse/api/observations/client.py +++ b/langfuse/api/observations/client.py @@ -4,11 +4,10 @@ import typing from ..commons.types.observation_level import ObservationLevel -from ..commons.types.observations_view import ObservationsView from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawObservationsClient, RawObservationsClient -from .types.observations_views import ObservationsViews +from .types.observations_v2response import ObservationsV2Response class ObservationsClient: @@ -26,53 +25,14 @@ def with_raw_response(self) -> RawObservationsClient: """ return self._raw_client - def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsView: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ObservationsView - - Examples - -------- - from langfuse import LangfuseAPI - - client = LangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - client.observations.get( - observation_id="observationId", - ) - """ - _response = self._raw_client.get( - observation_id, request_options=request_options - ) - return _response.data - def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -85,25 +45,64 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsViews: + ) -> ObservationsV2Response: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -164,6 +163,13 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -181,19 +187,13 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -224,7 +224,7 @@ def get_many( Returns ------- - ObservationsViews + ObservationsV2Response Examples -------- @@ -241,8 +241,11 @@ def get_many( client.observations.get_many() """ _response = self._raw_client.get_many( - page=page, + fields=fields, + expand_metadata=expand_metadata, limit=limit, + cursor=cursor, + parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, @@ -274,61 +277,14 @@ def with_raw_response(self) -> AsyncRawObservationsClient: """ return self._raw_client - async def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsView: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - ObservationsView - - Examples - -------- - import asyncio - - from langfuse import AsyncLangfuseAPI - - client = AsyncLangfuseAPI( - x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", - x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", - x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", - username="YOUR_USERNAME", - password="YOUR_PASSWORD", - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.observations.get( - observation_id="observationId", - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.get( - observation_id, request_options=request_options - ) - return _response.data - async def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -341,25 +297,64 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> ObservationsViews: + ) -> ObservationsV2Response: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -420,6 +415,13 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -437,19 +439,13 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -480,7 +476,7 @@ async def get_many( Returns ------- - ObservationsViews + ObservationsV2Response Examples -------- @@ -505,8 +501,11 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.get_many( - page=page, + fields=fields, + expand_metadata=expand_metadata, limit=limit, + cursor=cursor, + parse_io_as_json=parse_io_as_json, name=name, user_id=user_id, type=type, diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py index 508f8b082..3ae8eab15 100644 --- a/langfuse/api/observations/raw_client.py +++ b/langfuse/api/observations/raw_client.py @@ -10,130 +10,27 @@ from ..commons.errors.not_found_error import NotFoundError from ..commons.errors.unauthorized_error import UnauthorizedError from ..commons.types.observation_level import ObservationLevel -from ..commons.types.observations_view import ObservationsView from ..core.api_error import ApiError from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.datetime_utils import serialize_datetime from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.jsonable_encoder import jsonable_encoder from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions -from .types.observations_views import ObservationsViews +from .types.observations_v2response import ObservationsV2Response class RawObservationsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ObservationsView]: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[ObservationsView] - """ - _response = self._client_wrapper.httpx_client.request( - f"api/public/observations/{jsonable_encoder(observation_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - ObservationsView, - parse_obj_as( - type_=ObservationsView, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) - def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -146,25 +43,64 @@ def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ObservationsViews]: + ) -> HttpResponse[ObservationsV2Response]: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -225,6 +161,13 @@ def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -242,19 +185,13 @@ def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -285,14 +222,17 @@ def get_many( Returns ------- - HttpResponse[ObservationsViews] + HttpResponse[ObservationsV2Response] """ _response = self._client_wrapper.httpx_client.request( - "api/public/observations", + "api/public/v2/observations", method="GET", params={ - "page": page, + "fields": fields, + "expandMetadata": expand_metadata, "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -314,9 +254,9 @@ def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsViews, + ObservationsV2Response, parse_obj_as( - type_=ObservationsViews, # type: ignore + type_=ObservationsV2Response, # type: ignore object_=_response.json(), ), ) @@ -394,115 +334,14 @@ class AsyncRawObservationsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - async def get( - self, - observation_id: str, - *, - request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ObservationsView]: - """ - Get a observation - - Parameters - ---------- - observation_id : str - The unique langfuse identifier of an observation, can be an event, span or generation - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[ObservationsView] - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/public/observations/{jsonable_encoder(observation_id)}", - method="GET", - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - ObservationsView, - parse_obj_as( - type_=ObservationsView, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 400: - raise Error( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 401: - raise UnauthorizedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 403: - raise AccessDeniedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 405: - raise MethodNotAllowedError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - if _response.status_code == 404: - raise NotFoundError( - headers=dict(_response.headers), - body=typing.cast( - typing.Any, - parse_obj_as( - type_=typing.Any, # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response.text, - ) - raise ApiError( - status_code=_response.status_code, - headers=dict(_response.headers), - body=_response_json, - ) - async def get_many( self, *, - page: typing.Optional[int] = None, + fields: typing.Optional[str] = None, + expand_metadata: typing.Optional[str] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + parse_io_as_json: typing.Optional[bool] = None, name: typing.Optional[str] = None, user_id: typing.Optional[str] = None, type: typing.Optional[str] = None, @@ -515,25 +354,64 @@ async def get_many( version: typing.Optional[str] = None, filter: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ObservationsViews]: + ) -> AsyncHttpResponse[ObservationsV2Response]: """ - Get a list of observations. - - Consider using the [v2 observations endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) for cursor-based pagination and field selection. + Get a list of observations with cursor-based pagination and flexible field selection. + + ## Cursor-based Pagination + This endpoint uses cursor-based pagination for efficient traversal of large datasets. + The cursor is returned in the response metadata and should be passed in subsequent requests + to retrieve the next page of results. + + ## Field Selection + Use the `fields` parameter to control which observation fields are returned: + - `core` - Always included: id, traceId, startTime, endTime, projectId, parentObservationId, type + - `basic` - name, level, statusMessage, version, environment, bookmarked, public, userId, sessionId + - `time` - completionStartTime, createdAt, updatedAt + - `io` - input, output + - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) + - `model` - providedModelName, internalModelId, modelParameters + - `usage` - usageDetails, costDetails, totalCost + - `prompt` - promptId, promptName, promptVersion + - `metrics` - latency, timeToFirstToken + + If not specified, `core` and `basic` field groups are returned. + + ## Filters + Multiple filtering options are available via query parameters or the structured `filter` parameter. + When using the `filter` parameter, it takes precedence over individual query parameter filters. Parameters ---------- - page : typing.Optional[int] - Page number, starts at 1. + fields : typing.Optional[str] + Comma-separated list of field groups to include in the response. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + If not specified, `core` and `basic` field groups are returned. + Example: "basic,usage,model" + + expand_metadata : typing.Optional[str] + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Number of items to return per page. Maximum 1000, default 50. + + cursor : typing.Optional[str] + Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. + + parse_io_as_json : typing.Optional[bool] + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. name : typing.Optional[str] user_id : typing.Optional[str] type : typing.Optional[str] + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", "GUARDRAIL") trace_id : typing.Optional[str] @@ -594,6 +472,13 @@ async def get_many( - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - `statusMessage` (string) - Status message - `version` (string) - Version tag + - `userId` (string) - User ID + - `sessionId` (string) - Session ID + + ### Trace-Related Fields + - `traceName` (string) - Name of the parent trace + - `traceTags` (arrayOptions) - Tags from the parent trace + - `tags` (arrayOptions) - Alias for traceTags ### Performance Metrics - `latency` (number) - Latency in seconds (calculated: end_time - start_time) @@ -611,19 +496,13 @@ async def get_many( - `totalCost` (number) - Total cost in USD ### Model Information - - `model` (string) - Provided model name + - `model` (string) - Provided model name (alias: `providedModelName`) - `promptName` (string) - Associated prompt name - `promptVersion` (number) - Associated prompt version ### Structured Data - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - ### Associated Trace Fields (requires join with traces table) - - `userId` (string) - User ID from associated trace - - `traceName` (string) - Name from associated trace - - `traceEnvironment` (string) - Environment from associated trace - - `traceTags` (arrayOptions) - Tags from associated trace - ## Filter Examples ```json [ @@ -654,14 +533,17 @@ async def get_many( Returns ------- - AsyncHttpResponse[ObservationsViews] + AsyncHttpResponse[ObservationsV2Response] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/observations", + "api/public/v2/observations", method="GET", params={ - "page": page, + "fields": fields, + "expandMetadata": expand_metadata, "limit": limit, + "cursor": cursor, + "parseIoAsJson": parse_io_as_json, "name": name, "userId": user_id, "type": type, @@ -683,9 +565,9 @@ async def get_many( try: if 200 <= _response.status_code < 300: _data = typing.cast( - ObservationsViews, + ObservationsV2Response, parse_obj_as( - type_=ObservationsViews, # type: ignore + type_=ObservationsV2Response, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/observations/types/__init__.py b/langfuse/api/observations/types/__init__.py index 247b674a1..6e132aba6 100644 --- a/langfuse/api/observations/types/__init__.py +++ b/langfuse/api/observations/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .observations import Observations - from .observations_views import ObservationsViews + from .observations_v2meta import ObservationsV2Meta + from .observations_v2response import ObservationsV2Response _dynamic_imports: typing.Dict[str, str] = { - "Observations": ".observations", - "ObservationsViews": ".observations_views", + "ObservationsV2Meta": ".observations_v2meta", + "ObservationsV2Response": ".observations_v2response", } @@ -41,4 +41,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["Observations", "ObservationsViews"] +__all__ = ["ObservationsV2Meta", "ObservationsV2Response"] diff --git a/langfuse/api/observations_v2/types/observations_v2meta.py b/langfuse/api/observations/types/observations_v2meta.py similarity index 100% rename from langfuse/api/observations_v2/types/observations_v2meta.py rename to langfuse/api/observations/types/observations_v2meta.py diff --git a/langfuse/api/observations_v2/types/observations_v2response.py b/langfuse/api/observations/types/observations_v2response.py similarity index 100% rename from langfuse/api/observations_v2/types/observations_v2response.py rename to langfuse/api/observations/types/observations_v2response.py diff --git a/langfuse/api/score_v2/__init__.py b/langfuse/api/scores/__init__.py similarity index 100% rename from langfuse/api/score_v2/__init__.py rename to langfuse/api/scores/__init__.py diff --git a/langfuse/api/score_v2/client.py b/langfuse/api/scores/client.py similarity index 95% rename from langfuse/api/score_v2/client.py rename to langfuse/api/scores/client.py index 4ee5cf372..91db2c416 100644 --- a/langfuse/api/score_v2/client.py +++ b/langfuse/api/scores/client.py @@ -8,26 +8,26 @@ from ..commons.types.score_source import ScoreSource from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions -from .raw_client import AsyncRawScoreV2Client, RawScoreV2Client +from .raw_client import AsyncRawScoresClient, RawScoresClient from .types.get_scores_response import GetScoresResponse -class ScoreV2Client: +class ScoresClient: def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawScoreV2Client(client_wrapper=client_wrapper) + self._raw_client = RawScoresClient(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> RawScoreV2Client: + def with_raw_response(self) -> RawScoresClient: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - RawScoreV2Client + RawScoresClient """ return self._raw_client - def get( + def get_many( self, *, page: typing.Optional[int] = None, @@ -140,9 +140,9 @@ def get( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score_v2.get() + client.scores.get_many() """ - _response = self._raw_client.get( + _response = self._raw_client.get_many( page=page, limit=limit, user_id=user_id, @@ -198,7 +198,7 @@ def get_by_id( password="YOUR_PASSWORD", base_url="https://yourhost.com/path/to/api", ) - client.score_v2.get_by_id( + client.scores.get_by_id( score_id="scoreId", ) """ @@ -208,22 +208,22 @@ def get_by_id( return _response.data -class AsyncScoreV2Client: +class AsyncScoresClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawScoreV2Client(client_wrapper=client_wrapper) + self._raw_client = AsyncRawScoresClient(client_wrapper=client_wrapper) @property - def with_raw_response(self) -> AsyncRawScoreV2Client: + def with_raw_response(self) -> AsyncRawScoresClient: """ Retrieves a raw implementation of this client that returns raw responses. Returns ------- - AsyncRawScoreV2Client + AsyncRawScoresClient """ return self._raw_client - async def get( + async def get_many( self, *, page: typing.Optional[int] = None, @@ -341,12 +341,12 @@ async def get( async def main() -> None: - await client.score_v2.get() + await client.scores.get_many() asyncio.run(main()) """ - _response = await self._raw_client.get( + _response = await self._raw_client.get_many( page=page, limit=limit, user_id=user_id, @@ -407,7 +407,7 @@ async def get_by_id( async def main() -> None: - await client.score_v2.get_by_id( + await client.scores.get_by_id( score_id="scoreId", ) diff --git a/langfuse/api/score_v2/raw_client.py b/langfuse/api/scores/raw_client.py similarity index 99% rename from langfuse/api/score_v2/raw_client.py rename to langfuse/api/scores/raw_client.py index 2062b5bd9..2dc16e688 100644 --- a/langfuse/api/score_v2/raw_client.py +++ b/langfuse/api/scores/raw_client.py @@ -22,11 +22,11 @@ from .types.get_scores_response import GetScoresResponse -class RawScoreV2Client: +class RawScoresClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def get( + def get_many( self, *, page: typing.Optional[int] = None, @@ -339,11 +339,11 @@ def get_by_id( ) -class AsyncRawScoreV2Client: +class AsyncRawScoresClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - async def get( + async def get_many( self, *, page: typing.Optional[int] = None, diff --git a/langfuse/api/score_v2/types/__init__.py b/langfuse/api/scores/types/__init__.py similarity index 100% rename from langfuse/api/score_v2/types/__init__.py rename to langfuse/api/scores/types/__init__.py diff --git a/langfuse/api/score_v2/types/get_scores_response.py b/langfuse/api/scores/types/get_scores_response.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response.py rename to langfuse/api/scores/types/get_scores_response.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data.py b/langfuse/api/scores/types/get_scores_response_data.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data.py rename to langfuse/api/scores/types/get_scores_response_data.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_boolean.py b/langfuse/api/scores/types/get_scores_response_data_boolean.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_boolean.py rename to langfuse/api/scores/types/get_scores_response_data_boolean.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_categorical.py b/langfuse/api/scores/types/get_scores_response_data_categorical.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_categorical.py rename to langfuse/api/scores/types/get_scores_response_data_categorical.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_correction.py b/langfuse/api/scores/types/get_scores_response_data_correction.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_correction.py rename to langfuse/api/scores/types/get_scores_response_data_correction.py diff --git a/langfuse/api/score_v2/types/get_scores_response_data_numeric.py b/langfuse/api/scores/types/get_scores_response_data_numeric.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_data_numeric.py rename to langfuse/api/scores/types/get_scores_response_data_numeric.py diff --git a/langfuse/api/score_v2/types/get_scores_response_trace_data.py b/langfuse/api/scores/types/get_scores_response_trace_data.py similarity index 100% rename from langfuse/api/score_v2/types/get_scores_response_trace_data.py rename to langfuse/api/scores/types/get_scores_response_trace_data.py diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 0ed8a7458..d28fd085d 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -1173,7 +1173,7 @@ async def _fetch_batch_with_retry( ) # type: ignore return list(response.data) # type: ignore elif scope == "observations": - response = self.client.api.observations.get_many( + response = self.client.api.legacy.observations_v1.get_many( page=page, limit=limit, filter=filter, diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index adfa003df..10a6b0d80 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -50,7 +50,7 @@ async def update_generation(i, langfuse: Langfuse): api = get_api() for i in range(100): # Find the observations with the expected name - observations = api.observations.get_many(name=str(i)).data + observations = api.legacy.observations_v1.get_many(name=str(i)).data # Find generation observations (there should be at least one) generation_obs = [obs for obs in observations if obs.type == "GENERATION"] @@ -962,7 +962,7 @@ def test_create_span_and_get_observation(): sleep(2) # Use API to fetch the observation by ID - observation = get_api().observations.get(span_id) + observation = get_api().legacy.observations_v1.get(span_id) # Verify observation properties assert observation.name == "span" @@ -1397,7 +1397,7 @@ def test_get_generations(): sleep(3) # Fetch generations using API - generations = get_api().observations.get_many(name=generation_name) + generations = get_api().legacy.observations_v1.get_many(name=generation_name) # Verify fetched generation matches what we created assert len(generations.data) == 1 @@ -1436,7 +1436,9 @@ def test_get_generations_by_user(): sleep(3) # Fetch generations by user ID using the API - generations = get_api().observations.get_many(user_id=user_id, type="GENERATION") + generations = get_api().legacy.observations_v1.get_many( + user_id=user_id, type="GENERATION" + ) # Verify fetched generation matches what we created assert len(generations.data) == 1 @@ -1474,7 +1476,7 @@ def test_kwargs(): sleep(2) # Retrieve and verify - observation = get_api().observations.get(span_id) + observation = get_api().legacy.observations_v1.get(span_id) # Verify kwargs were properly set as attributes assert observation.start_time is not None @@ -1738,7 +1740,7 @@ def test_get_observation(): sleep(2) # Fetch the observation using the API - observation = get_api().observations.get(generation_id) + observation = get_api().legacy.observations_v1.get(generation_id) # Verify observation properties assert observation.id == generation_id @@ -1770,7 +1772,7 @@ def test_get_observations(): sleep(2) # Fetch observations using the API - observations = get_api().observations.get_many(name=name, limit=10) + observations = get_api().legacy.observations_v1.get_many(name=name, limit=10) # Verify fetched observations assert len(observations.data) == 2 @@ -1785,7 +1787,9 @@ def test_get_observations(): assert gen2_id in gen_ids # Test pagination - paginated_response = get_api().observations.get_many(name=name, limit=1, page=2) + paginated_response = get_api().legacy.observations_v1.get_many( + name=name, limit=1, page=2 + ) assert len(paginated_response.data) == 1 assert paginated_response.meta.total_items == 2 # Parent span + 2 generations assert paginated_response.meta.total_pages == 2 @@ -1800,7 +1804,7 @@ def test_get_trace_not_found(): def test_get_observation_not_found(): # Attempt to fetch a non-existent observation using the API with pytest.raises(Exception): - get_api().observations.get(create_uuid()) + get_api().legacy.observations_v1.get(create_uuid()) def test_get_traces_empty(): @@ -1813,7 +1817,7 @@ def test_get_traces_empty(): def test_get_observations_empty(): # Fetch observations with a filter that should return no results - response = get_api().observations.get_many(name=create_uuid()) + response = get_api().legacy.observations_v1.get_many(name=create_uuid()) assert len(response.data) == 0 assert response.meta.total_items == 0 diff --git a/tests/test_openai.py b/tests/test_openai.py index f24bf93cf..8706bae13 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -39,7 +39,7 @@ def test_openai_chat_completion(openai): sleep(1) - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -96,7 +96,7 @@ def test_openai_chat_completion_stream(openai): langfuse.flush() sleep(3) - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -156,7 +156,7 @@ def test_openai_chat_completion_stream_with_next_iteration(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -205,7 +205,7 @@ def test_openai_chat_completion_stream_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -255,7 +255,7 @@ def test_openai_chat_completion_with_langfuse_prompt(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -278,7 +278,7 @@ def test_openai_chat_completion_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -338,7 +338,7 @@ def test_openai_chat_completion_two_calls(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -348,7 +348,7 @@ def test_openai_chat_completion_two_calls(openai): assert generation.data[0].input == [{"content": "1 + 1 = ", "role": "user"}] - generation_2 = get_api().observations.get_many( + generation_2 = get_api().legacy.observations_v1.get_many( name=generation_name_2, type="GENERATION" ) @@ -372,7 +372,7 @@ def test_openai_chat_completion_with_seed(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -399,7 +399,7 @@ def test_openai_completion(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -447,7 +447,7 @@ def test_openai_completion_stream(openai): assert len(content) > 0 - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -496,7 +496,7 @@ def test_openai_completion_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -539,7 +539,7 @@ def test_openai_completion_stream_fail(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -588,7 +588,7 @@ def test_openai_completion_with_langfuse_prompt(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -630,7 +630,7 @@ async def test_async_chat(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -676,7 +676,7 @@ async def test_async_chat_stream(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -734,7 +734,7 @@ async def test_async_chat_stream_with_anext(openai): print(result) - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -796,7 +796,7 @@ class StepByStepAIResponse(BaseModel): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -840,7 +840,7 @@ class StepByStepAIResponse(BaseModel): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -884,7 +884,7 @@ def test_openai_tool_call(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -940,7 +940,7 @@ def test_openai_tool_call_streamed(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1018,7 +1018,7 @@ def test_structured_output_response_format_kwarg(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1087,7 +1087,7 @@ class CalendarEvent(BaseModel): if Version(openai.__version__) >= Version("1.50.0"): # Check the trace and observation properties - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1133,7 +1133,7 @@ async def test_close_async_stream(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1194,7 +1194,7 @@ def test_base_64_image_input(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1245,7 +1245,7 @@ def test_audio_input_and_output(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1285,7 +1285,7 @@ def test_response_api_text_input(openai): ) langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1331,7 +1331,7 @@ def test_response_api_image_input(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1363,7 +1363,7 @@ def test_response_api_web_search(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1400,7 +1400,7 @@ def test_response_api_streaming(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1454,7 +1454,7 @@ def test_response_api_functions(openai): langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1486,7 +1486,7 @@ def test_response_api_reasoning(openai): ) langfuse.flush() - generation = get_api().observations.get_many( + generation = get_api().legacy.observations_v1.get_many( name=generation_name, type="GENERATION" ) @@ -1518,7 +1518,9 @@ def test_openai_embeddings(openai): langfuse.flush() sleep(1) - embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + embedding = get_api().legacy.observations_v1.get_many( + name=embedding_name, type="EMBEDDING" + ) assert len(embedding.data) != 0 embedding_data = embedding.data[0] @@ -1552,7 +1554,9 @@ def test_openai_embeddings_multiple_inputs(openai): langfuse.flush() sleep(1) - embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + embedding = get_api().legacy.observations_v1.get_many( + name=embedding_name, type="EMBEDDING" + ) assert len(embedding.data) != 0 embedding_data = embedding.data[0] @@ -1583,7 +1587,9 @@ async def test_async_openai_embeddings(openai): langfuse.flush() sleep(1) - embedding = get_api().observations.get_many(name=embedding_name, type="EMBEDDING") + embedding = get_api().legacy.observations_v1.get_many( + name=embedding_name, type="EMBEDDING" + ) assert len(embedding.data) != 0 embedding_data = embedding.data[0] diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 6aabb1a96..3c4c5c013 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -676,7 +676,7 @@ def test_prompt_end_to_end(): generation = trace.observations[0] assert generation.prompt_id is not None - observation = api.observations.get(generation.id) + observation = api.legacy.observations_v1.get(generation.id) assert observation.prompt_id is not None From 45768978ab111189af9dcd3b98db23019e7f1523 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:04:03 +0000 Subject: [PATCH 205/296] chore(deps-dev): bump langgraph from 1.0.9 to 1.0.10rc1 (#1560) Bumps [langgraph](https://github.com/langchain-ai/langgraph) from 1.0.9 to 1.0.10rc1. - [Release notes](https://github.com/langchain-ai/langgraph/releases) - [Commits](https://github.com/langchain-ai/langgraph/compare/1.0.9...1.0.10rc1) --- updated-dependencies: - dependency-name: langgraph dependency-version: 1.0.10rc1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index ce10280e5..8c899de2c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -748,14 +748,14 @@ tiktoken = ">=0.7.0,<1.0.0" [[package]] name = "langgraph" -version = "1.0.9" +version = "1.0.10" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "langgraph-1.0.9-py3-none-any.whl", hash = "sha256:bce0d1f3e9a20434215a2a818395a58aedfc11c87bd6b52706c0db5c05ec44ec"}, - {file = "langgraph-1.0.9.tar.gz", hash = "sha256:feac2729faba7d3c325bef76f240d7d7f66b02d2cbf4fdb1ed7d0cc83f963651"}, + {file = "langgraph-1.0.10-py3-none-any.whl", hash = "sha256:7c298bef4f6ea292fcf9824d6088fe41a6727e2904ad6066f240c4095af12247"}, + {file = "langgraph-1.0.10.tar.gz", hash = "sha256:73bd10ee14a8020f31ef07e9cd4c1a70c35cc07b9c2b9cd637509a10d9d51e29"}, ] [package.dependencies] From adbfe0b7b9a88a61eb5f1e1500cd924459f83198 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 10 Mar 2026 16:10:34 +0000 Subject: [PATCH 206/296] chore: release v5.0.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 8d6183e0c..a4619f596 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "4.0.0b1" +__version__ = "5.0.0" diff --git a/pyproject.toml b/pyproject.toml index 8437cfe49..313ad4d30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "4.0.0b1" +version = "5.0.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 96a1f8b505dfc6389b8bcf11becab71a5dbf2e87 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:13:26 +0100 Subject: [PATCH 207/296] Revert "chore: release v5.0.0" This reverts commit adbfe0b7b9a88a61eb5f1e1500cd924459f83198. --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index a4619f596..8d6183e0c 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "5.0.0" +__version__ = "4.0.0b1" diff --git a/pyproject.toml b/pyproject.toml index 313ad4d30..8437cfe49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "5.0.0" +version = "4.0.0b1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 18418fd2c66344482d8237ab01ba180f07f46bed Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:16:18 +0100 Subject: [PATCH 208/296] chore: reset version to latest on pypi --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 8d6183e0c..d1e9207dd 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "4.0.0b1" +__version__ = "3.14.5" diff --git a/pyproject.toml b/pyproject.toml index 8437cfe49..b632cdebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "4.0.0b1" +version = "3.14.5" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 83d95bdd12e18012a022202d85cb8ed65367e729 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 10 Mar 2026 16:21:38 +0000 Subject: [PATCH 209/296] chore: release v4.0.0 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index d1e9207dd..9b67f697f 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "3.14.5" +__version__ = "4.0.0" diff --git a/pyproject.toml b/pyproject.toml index b632cdebc..4509f31c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "3.14.5" +version = "4.0.0" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From c16eb0b0eef07cb76ea171d77d5ba378c47cc793 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 12 Mar 2026 17:58:50 +0100 Subject: [PATCH 210/296] feat(api): update API spec from langfuse/langfuse e84ca45 (#1562) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 6 + .../api/blob_storage_integrations/__init__.py | 6 + .../api/blob_storage_integrations/client.py | 87 ++++++++ .../blob_storage_integrations/raw_client.py | 203 ++++++++++++++++++ .../types/__init__.py | 8 + .../blob_storage_integration_response.py | 6 + ...lob_storage_integration_status_response.py | 50 +++++ .../types/blob_storage_sync_status.py | 47 ++++ langfuse/api/prompts/client.py | 20 +- langfuse/api/prompts/raw_client.py | 10 + langfuse/api/prompts/types/base_prompt.py | 2 +- 11 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 7322f3e71..443f5cdd2 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -55,8 +55,10 @@ BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationResponse, + BlobStorageIntegrationStatusResponse, BlobStorageIntegrationType, BlobStorageIntegrationsResponse, + BlobStorageSyncStatus, CreateBlobStorageIntegrationRequest, ) from .client import AsyncLangfuseAPI, LangfuseAPI @@ -309,8 +311,10 @@ "BlobStorageIntegrationDeletionResponse": ".blob_storage_integrations", "BlobStorageIntegrationFileType": ".blob_storage_integrations", "BlobStorageIntegrationResponse": ".blob_storage_integrations", + "BlobStorageIntegrationStatusResponse": ".blob_storage_integrations", "BlobStorageIntegrationType": ".blob_storage_integrations", "BlobStorageIntegrationsResponse": ".blob_storage_integrations", + "BlobStorageSyncStatus": ".blob_storage_integrations", "BooleanScore": ".commons", "BooleanScoreV1": ".commons", "BulkConfig": ".scim", @@ -594,8 +598,10 @@ def __dir__(): "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", + "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", "BlobStorageIntegrationsResponse", + "BlobStorageSyncStatus", "BooleanScore", "BooleanScoreV1", "BulkConfig", diff --git a/langfuse/api/blob_storage_integrations/__init__.py b/langfuse/api/blob_storage_integrations/__init__.py index abd0d9e84..266be2a6c 100644 --- a/langfuse/api/blob_storage_integrations/__init__.py +++ b/langfuse/api/blob_storage_integrations/__init__.py @@ -12,8 +12,10 @@ BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationResponse, + BlobStorageIntegrationStatusResponse, BlobStorageIntegrationType, BlobStorageIntegrationsResponse, + BlobStorageSyncStatus, CreateBlobStorageIntegrationRequest, ) _dynamic_imports: typing.Dict[str, str] = { @@ -22,8 +24,10 @@ "BlobStorageIntegrationDeletionResponse": ".types", "BlobStorageIntegrationFileType": ".types", "BlobStorageIntegrationResponse": ".types", + "BlobStorageIntegrationStatusResponse": ".types", "BlobStorageIntegrationType": ".types", "BlobStorageIntegrationsResponse": ".types", + "BlobStorageSyncStatus": ".types", "CreateBlobStorageIntegrationRequest": ".types", } @@ -61,7 +65,9 @@ def __dir__(): "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", + "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", "BlobStorageIntegrationsResponse", + "BlobStorageSyncStatus", "CreateBlobStorageIntegrationRequest", ] diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index 6b1f6a677..c79bc82c2 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -16,6 +16,9 @@ ) from .types.blob_storage_integration_file_type import BlobStorageIntegrationFileType from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integration_status_response import ( + BlobStorageIntegrationStatusResponse, +) from .types.blob_storage_integration_type import BlobStorageIntegrationType from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse @@ -192,6 +195,44 @@ def upsert_blob_storage_integration( ) return _response.data + def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationStatusResponse: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationStatusResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.blob_storage_integrations.get_blob_storage_integration_status( + id="id", + ) + """ + _response = self._raw_client.get_blob_storage_integration_status( + id, request_options=request_options + ) + return _response.data + def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> BlobStorageIntegrationDeletionResponse: @@ -416,6 +457,52 @@ async def main() -> None: ) return _response.data + async def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> BlobStorageIntegrationStatusResponse: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BlobStorageIntegrationStatusResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.blob_storage_integrations.get_blob_storage_integration_status( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_blob_storage_integration_status( + id, request_options=request_options + ) + return _response.data + async def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> BlobStorageIntegrationDeletionResponse: diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 00ee72316..69143f4d6 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -22,6 +22,9 @@ ) from .types.blob_storage_integration_file_type import BlobStorageIntegrationFileType from .types.blob_storage_integration_response import BlobStorageIntegrationResponse +from .types.blob_storage_integration_status_response import ( + BlobStorageIntegrationStatusResponse, +) from .types.blob_storage_integration_type import BlobStorageIntegrationType from .types.blob_storage_integrations_response import BlobStorageIntegrationsResponse @@ -300,6 +303,106 @@ def upsert_blob_storage_integration( body=_response_json, ) + def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[BlobStorageIntegrationStatusResponse]: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[BlobStorageIntegrationStatusResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationStatusResponse, + parse_obj_as( + type_=BlobStorageIntegrationStatusResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[BlobStorageIntegrationDeletionResponse]: @@ -672,6 +775,106 @@ async def upsert_blob_storage_integration( body=_response_json, ) + async def get_blob_storage_integration_status( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[BlobStorageIntegrationStatusResponse]: + """ + Get the sync status of a blob storage integration by integration ID (requires organization-scoped API key) + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[BlobStorageIntegrationStatusResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/integrations/blob-storage/{jsonable_encoder(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BlobStorageIntegrationStatusResponse, + parse_obj_as( + type_=BlobStorageIntegrationStatusResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + async def delete_blob_storage_integration( self, id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[BlobStorageIntegrationDeletionResponse]: diff --git a/langfuse/api/blob_storage_integrations/types/__init__.py b/langfuse/api/blob_storage_integrations/types/__init__.py index cc19f1a6d..e0fe3e9ff 100644 --- a/langfuse/api/blob_storage_integrations/types/__init__.py +++ b/langfuse/api/blob_storage_integrations/types/__init__.py @@ -13,8 +13,12 @@ ) from .blob_storage_integration_file_type import BlobStorageIntegrationFileType from .blob_storage_integration_response import BlobStorageIntegrationResponse + from .blob_storage_integration_status_response import ( + BlobStorageIntegrationStatusResponse, + ) from .blob_storage_integration_type import BlobStorageIntegrationType from .blob_storage_integrations_response import BlobStorageIntegrationsResponse + from .blob_storage_sync_status import BlobStorageSyncStatus from .create_blob_storage_integration_request import ( CreateBlobStorageIntegrationRequest, ) @@ -24,8 +28,10 @@ "BlobStorageIntegrationDeletionResponse": ".blob_storage_integration_deletion_response", "BlobStorageIntegrationFileType": ".blob_storage_integration_file_type", "BlobStorageIntegrationResponse": ".blob_storage_integration_response", + "BlobStorageIntegrationStatusResponse": ".blob_storage_integration_status_response", "BlobStorageIntegrationType": ".blob_storage_integration_type", "BlobStorageIntegrationsResponse": ".blob_storage_integrations_response", + "BlobStorageSyncStatus": ".blob_storage_sync_status", "CreateBlobStorageIntegrationRequest": ".create_blob_storage_integration_request", } @@ -63,7 +69,9 @@ def __dir__(): "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", + "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", "BlobStorageIntegrationsResponse", + "BlobStorageSyncStatus", "CreateBlobStorageIntegrationRequest", ] diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py index 08529ee67..c0536f14e 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py @@ -46,6 +46,12 @@ class BlobStorageIntegrationResponse(UniversalBaseModel): last_sync_at: typing_extensions.Annotated[ typing.Optional[dt.datetime], FieldMetadata(alias="lastSyncAt") ] = None + last_error: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="lastError") + ] = None + last_error_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastErrorAt") + ] = None created_at: typing_extensions.Annotated[ dt.datetime, FieldMetadata(alias="createdAt") ] diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py new file mode 100644 index 000000000..951074990 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_status_response.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .blob_storage_sync_status import BlobStorageSyncStatus + + +class BlobStorageIntegrationStatusResponse(UniversalBaseModel): + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + sync_status: typing_extensions.Annotated[ + BlobStorageSyncStatus, FieldMetadata(alias="syncStatus") + ] + enabled: bool + last_sync_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastSyncAt") + ] = pydantic.Field(default=None) + """ + End of the last successfully exported time window. Compare against your ETL bookmark to determine if new data is available. Null if the integration has never synced. + """ + + next_sync_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="nextSyncAt") + ] = pydantic.Field(default=None) + """ + When the next export is scheduled. Null if no sync has occurred yet. + """ + + last_error: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="lastError") + ] = pydantic.Field(default=None) + """ + Raw error message from the storage provider (S3/Azure/GCS) if the last export failed. Cleared on successful export. + """ + + last_error_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="lastErrorAt") + ] = pydantic.Field(default=None) + """ + When the last error occurred. Cleared on successful export. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py new file mode 100644 index 000000000..254e06645 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageSyncStatus(enum.StrEnum): + """ + Sync status of the blob storage integration: + - `disabled` — integration is not enabled + - `error` — last export failed (see `lastError` for details) + - `idle` — enabled but has never exported yet + - `queued` — next export is overdue (`nextSyncAt` is in the past) and waiting to be picked up by the worker + - `up_to_date` — all available data has been exported; next export is scheduled for the future + + **ETL usage**: poll this endpoint and check for `up_to_date` status. Compare `lastSyncAt` against your + ETL bookmark to determine if new data is available. Note that exports run with a 30-minute lag buffer, + so `lastSyncAt` will always be at least 30 minutes behind real-time. + """ + + IDLE = "idle" + QUEUED = "queued" + UP_TO_DATE = "up_to_date" + DISABLED = "disabled" + ERROR = "error" + + def visit( + self, + idle: typing.Callable[[], T_Result], + queued: typing.Callable[[], T_Result], + up_to_date: typing.Callable[[], T_Result], + disabled: typing.Callable[[], T_Result], + error: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageSyncStatus.IDLE: + return idle() + if self is BlobStorageSyncStatus.QUEUED: + return queued() + if self is BlobStorageSyncStatus.UP_TO_DATE: + return up_to_date() + if self is BlobStorageSyncStatus.DISABLED: + return disabled() + if self is BlobStorageSyncStatus.ERROR: + return error() diff --git a/langfuse/api/prompts/client.py b/langfuse/api/prompts/client.py index fc6203787..eff5e572f 100644 --- a/langfuse/api/prompts/client.py +++ b/langfuse/api/prompts/client.py @@ -35,6 +35,7 @@ def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Prompt: """ @@ -52,6 +53,9 @@ def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -76,7 +80,11 @@ def get( ) """ _response = self._raw_client.get( - prompt_name, version=version, label=label, request_options=request_options + prompt_name, + version=version, + label=label, + resolve=resolve, + request_options=request_options, ) return _response.data @@ -279,6 +287,7 @@ async def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Prompt: """ @@ -296,6 +305,9 @@ async def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -328,7 +340,11 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.get( - prompt_name, version=version, label=label, request_options=request_options + prompt_name, + version=version, + label=label, + resolve=resolve, + request_options=request_options, ) return _response.data diff --git a/langfuse/api/prompts/raw_client.py b/langfuse/api/prompts/raw_client.py index 81b108968..a2d81a9d1 100644 --- a/langfuse/api/prompts/raw_client.py +++ b/langfuse/api/prompts/raw_client.py @@ -35,6 +35,7 @@ def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Prompt]: """ @@ -52,6 +53,9 @@ def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -65,6 +69,7 @@ def get( params={ "version": version, "label": label, + "resolve": resolve, }, request_options=request_options, ) @@ -511,6 +516,7 @@ async def get( *, version: typing.Optional[int] = None, label: typing.Optional[str] = None, + resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Prompt]: """ @@ -528,6 +534,9 @@ async def get( label : typing.Optional[str] Label of the prompt to be retrieved. Defaults to "production" if no label or version is set. + resolve : typing.Optional[bool] + Resolve prompt dependencies before returning the prompt. Defaults to `true`. Set to `false` to return the raw stored prompt with dependency tags intact. This bypasses prompt caching and is intended for debugging or one-off jobs, not production runtime fetches. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -541,6 +550,7 @@ async def get( params={ "version": version, "label": label, + "resolve": resolve, }, request_options=request_options, ) diff --git a/langfuse/api/prompts/types/base_prompt.py b/langfuse/api/prompts/types/base_prompt.py index bd9461600..73cc4a12e 100644 --- a/langfuse/api/prompts/types/base_prompt.py +++ b/langfuse/api/prompts/types/base_prompt.py @@ -34,7 +34,7 @@ class BasePrompt(UniversalBaseModel): FieldMetadata(alias="resolutionGraph"), ] = pydantic.Field(default=None) """ - The dependency resolution graph for the current prompt. Null if prompt has no dependencies. + The dependency resolution graph for the current prompt. Null if the prompt has no dependencies or if `resolve=false` was used. """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( From caddeff989b35fbfe7d115151c7c98a3cf3c3565 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:59:19 +0100 Subject: [PATCH 211/296] chore(deps-dev): bump orjson from 3.11.3 to 3.11.6 (#1563) Bumps [orjson](https://github.com/ijl/orjson) from 3.11.3 to 3.11.6. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.11.3...3.11.6) --- updated-dependencies: - dependency-name: orjson dependency-version: 3.11.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 161 +++++++++++++++++++++++++--------------------------- 1 file changed, 76 insertions(+), 85 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8c899de2c..6344850d6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1193,95 +1193,86 @@ typing-extensions = ">=4.5.0" [[package]] name = "orjson" -version = "3.11.3" +version = "3.11.6" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6"}, - {file = "orjson-3.11.3-cp310-cp310-win32.whl", hash = "sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc"}, - {file = "orjson-3.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770"}, - {file = "orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f"}, - {file = "orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f"}, - {file = "orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204"}, - {file = "orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b"}, - {file = "orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e"}, - {file = "orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b"}, - {file = "orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049"}, - {file = "orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca"}, - {file = "orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1"}, - {file = "orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710"}, - {file = "orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810"}, - {file = "orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633"}, - {file = "orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b"}, - {file = "orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae"}, - {file = "orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce"}, - {file = "orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4"}, - {file = "orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e"}, - {file = "orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d"}, - {file = "orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872"}, - {file = "orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d"}, - {file = "orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804"}, - {file = "orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc"}, - {file = "orjson-3.11.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:56afaf1e9b02302ba636151cfc49929c1bb66b98794291afd0e5f20fecaf757c"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913f629adef31d2d350d41c051ce7e33cf0fd06a5d1cb28d49b1899b23b903aa"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0a23b41f8f98b4e61150a03f83e4f0d566880fe53519d445a962929a4d21045"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d721fee37380a44f9d9ce6c701b3960239f4fb3d5ceea7f31cbd43882edaa2f"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73b92a5b69f31b1a58c0c7e31080aeaec49c6e01b9522e71ff38d08f15aa56de"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2489b241c19582b3f1430cc5d732caefc1aaf378d97e7fb95b9e56bed11725f"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5189a5dab8b0312eadaf9d58d3049b6a52c454256493a557405e77a3d67ab7f"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9d8787bdfbb65a85ea76d0e96a3b1bed7bf0fbcb16d40408dc1172ad784a49d2"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:8e531abd745f51f8035e207e75e049553a86823d189a51809c078412cefb399a"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8ab962931015f170b97a3dd7bd933399c1bae8ed8ad0fb2a7151a5654b6941c7"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:124d5ba71fee9c9902c4a7baa9425e663f7f0aecf73d31d54fe3dd357d62c1a7"}, - {file = "orjson-3.11.3-cp39-cp39-win32.whl", hash = "sha256:22724d80ee5a815a44fc76274bb7ba2e7464f5564aacb6ecddaa9970a83e3225"}, - {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, - {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, + {file = "orjson-3.11.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a613fc37e007143d5b6286dccb1394cd114b07832417006a02b620ddd8279e37"}, + {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46ebee78f709d3ba7a65384cfe285bb0763157c6d2f836e7bde2f12d33a867a2"}, + {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a726fa86d2368cd57990f2bd95ef5495a6e613b08fc9585dfe121ec758fb08d1"}, + {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:150f12e59d6864197770c78126e1a6e07a3da73d1728731bf3bc1e8b96ffdbe6"}, + {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2d9746a5b5ce20c0908ada451eb56da4ffa01552a50789a0354d8636a02953"}, + {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd177f5dd91666d31e9019f1b06d2fcdf8a409a1637ddcb5915085dede85680"}, + {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d777ec41a327bd3b7de97ba7bce12cc1007815ca398e4e4de9ec56c022c090b"}, + {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f3a135f83185c87c13ff231fcb7dbb2fa4332a376444bd65135b50ff4cc5265c"}, + {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:2a8eeed7d4544cf391a142b0dd06029dac588e96cc692d9ab1c3f05b1e57c7f6"}, + {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9d576865a21e5cc6695be8fb78afc812079fd361ce6a027a7d41561b61b33a90"}, + {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:925e2df51f60aa50f8797830f2adfc05330425803f4105875bb511ced98b7f89"}, + {file = "orjson-3.11.6-cp310-cp310-win32.whl", hash = "sha256:09dded2de64e77ac0b312ad59f35023548fb87393a57447e1bb36a26c181a90f"}, + {file = "orjson-3.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:3a63b5e7841ca8635214c6be7c0bf0246aa8c5cd4ef0c419b14362d0b2fb13de"}, + {file = "orjson-3.11.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e259e85a81d76d9665f03d6129e09e4435531870de5961ddcd0bf6e3a7fde7d7"}, + {file = "orjson-3.11.6-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:52263949f41b4a4822c6b1353bcc5ee2f7109d53a3b493501d3369d6d0e7937a"}, + {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6439e742fa7834a24698d358a27346bb203bff356ae0402e7f5df8f749c621a8"}, + {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b81ffd68f084b4e993e3867acb554a049fa7787cc8710bbcc1e26965580d99be"}, + {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5a5468e5e60f7ef6d7f9044b06c8f94a3c56ba528c6e4f7f06ae95164b595ec"}, + {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72c5005eb45bd2535632d4f3bec7ad392832cfc46b62a3021da3b48a67734b45"}, + {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b14dd49f3462b014455a28a4d810d3549bf990567653eb43765cd847df09145"}, + {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bb2c1ea30ef302f0f89f9bf3e7f9ab5e2af29dc9f80eb87aa99788e4e2d65"}, + {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:825e0a85d189533c6bff7e2fc417a28f6fcea53d27125c4551979aecd6c9a197"}, + {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b04575417a26530637f6ab4b1f7b4f666eb0433491091da4de38611f97f2fcf3"}, + {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b83eb2e40e8c4da6d6b340ee6b1d6125f5195eb1b0ebb7eac23c6d9d4f92d224"}, + {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1f42da604ee65a6b87eef858c913ce3e5777872b19321d11e6fc6d21de89b64f"}, + {file = "orjson-3.11.6-cp311-cp311-win32.whl", hash = "sha256:5ae45df804f2d344cffb36c43fdf03c82fb6cd247f5faa41e21891b40dfbf733"}, + {file = "orjson-3.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:f4295948d65ace0a2d8f2c4ccc429668b7eb8af547578ec882e16bf79b0050b2"}, + {file = "orjson-3.11.6-cp311-cp311-win_arm64.whl", hash = "sha256:314e9c45e0b81b547e3a1cfa3df3e07a815821b3dac9fe8cb75014071d0c16a4"}, + {file = "orjson-3.11.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6f03f30cd8953f75f2a439070c743c7336d10ee940da918d71c6f3556af3ddcf"}, + {file = "orjson-3.11.6-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:af44baae65ef386ad971469a8557a0673bb042b0b9fd4397becd9c2dfaa02588"}, + {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c310a48542094e4f7dbb6ac076880994986dda8ca9186a58c3cb70a3514d3231"}, + {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8dfa7a5d387f15ecad94cb6b2d2d5f4aeea64efd8d526bfc03c9812d01e1cc0"}, + {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba8daee3e999411b50f8b50dbb0a3071dd1845f3f9a1a0a6fa6de86d1689d84d"}, + {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89d104c974eafd7436d7a5fdbc57f7a1e776789959a2f4f1b2eab5c62a339f4"}, + {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e2e2456788ca5ea75616c40da06fc885a7dc0389780e8a41bf7c5389ba257b"}, + {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a42efebc45afabb1448001e90458c4020d5c64fbac8a8dc4045b777db76cb5a"}, + {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71b7cbef8471324966c3738c90ba38775563ef01b512feb5ad4805682188d1b9"}, + {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f8515e5910f454fe9a8e13c2bb9dc4bae4c1836313e967e72eb8a4ad874f0248"}, + {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:300360edf27c8c9bf7047345a94fddf3a8b8922df0ff69d71d854a170cb375cf"}, + {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:caaed4dad39e271adfadc106fab634d173b2bb23d9cf7e67bd645f879175ebfc"}, + {file = "orjson-3.11.6-cp312-cp312-win32.whl", hash = "sha256:955368c11808c89793e847830e1b1007503a5923ddadc108547d3b77df761044"}, + {file = "orjson-3.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:2c68de30131481150073d90a5d227a4a421982f42c025ecdfb66157f9579e06f"}, + {file = "orjson-3.11.6-cp312-cp312-win_arm64.whl", hash = "sha256:65dfa096f4e3a5e02834b681f539a87fbe85adc82001383c0db907557f666bfc"}, + {file = "orjson-3.11.6-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e4ae1670caabb598a88d385798692ce2a1b2f078971b3329cfb85253c6097f5b"}, + {file = "orjson-3.11.6-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:2c6b81f47b13dac2caa5d20fbc953c75eb802543abf48403a4703ed3bff225f0"}, + {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:647d6d034e463764e86670644bdcaf8e68b076e6e74783383b01085ae9ab334f"}, + {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8523b9cc4ef174ae52414f7699e95ee657c16aa18b3c3c285d48d7966cce9081"}, + {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:313dfd7184cde50c733fc0d5c8c0e2f09017b573afd11dc36bd7476b30b4cb17"}, + {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905ee036064ff1e1fd1fb800055ac477cdcb547a78c22c1bc2bbf8d5d1a6fb42"}, + {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce374cb98411356ba906914441fc993f271a7a666d838d8de0e0900dd4a4bc12"}, + {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cded072b9f65fcfd188aead45efa5bd528ba552add619b3ad2a81f67400ec450"}, + {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ab85bdbc138e1f73a234db6bb2e4cc1f0fcec8f4bd2bd2430e957a01aadf746"}, + {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:351b96b614e3c37a27b8ab048239ebc1e0be76cc17481a430d70a77fb95d3844"}, + {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f9959c85576beae5cdcaaf39510b15105f1ee8b70d5dacd90152617f57be8c83"}, + {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75682d62b1b16b61a30716d7a2ec1f4c36195de4a1c61f6665aedd947b93a5d5"}, + {file = "orjson-3.11.6-cp313-cp313-win32.whl", hash = "sha256:40dc277999c2ef227dcc13072be879b4cfd325502daeb5c35ed768f706f2bf30"}, + {file = "orjson-3.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:f0f6e9f8ff7905660bc3c8a54cd4a675aa98f7f175cf00a59815e2ff42c0d916"}, + {file = "orjson-3.11.6-cp313-cp313-win_arm64.whl", hash = "sha256:1608999478664de848e5900ce41f25c4ecdfc4beacbc632b6fd55e1a586e5d38"}, + {file = "orjson-3.11.6-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6026db2692041d2a23fe2545606df591687787825ad5821971ef0974f2c47630"}, + {file = "orjson-3.11.6-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:132b0ab2e20c73afa85cf142e547511feb3d2f5b7943468984658f3952b467d4"}, + {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b376fb05f20a96ec117d47987dd3b39265c635725bda40661b4c5b73b77b5fde"}, + {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:954dae4e080574672a1dfcf2a840eddef0f27bd89b0e94903dd0824e9c1db060"}, + {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe515bb89d59e1e4b48637a964f480b35c0a2676de24e65e55310f6016cca7ce"}, + {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:380f9709c275917af28feb086813923251e11ee10687257cd7f1ea188bcd4485"}, + {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8173e0d3f6081e7034c51cf984036d02f6bab2a2126de5a759d79f8e5a140e7"}, + {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dddf9ba706294906c56ef5150a958317b09aa3a8a48df1c52ccf22ec1907eac"}, + {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cbae5c34588dc79938dffb0b6fbe8c531f4dc8a6ad7f39759a9eb5d2da405ef2"}, + {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f75c318640acbddc419733b57f8a07515e587a939d8f54363654041fd1f4e465"}, + {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e0ab8d13aa2a3e98b4a43487c9205b2c92c38c054b4237777484d503357c8437"}, + {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f884c7fb1020d44612bd7ac0db0babba0e2f78b68d9a650c7959bf99c783773f"}, + {file = "orjson-3.11.6-cp314-cp314-win32.whl", hash = "sha256:8d1035d1b25732ec9f971e833a3e299d2b1a330236f75e6fd945ad982c76aaf3"}, + {file = "orjson-3.11.6-cp314-cp314-win_amd64.whl", hash = "sha256:931607a8865d21682bb72de54231655c86df1870502d2962dbfd12c82890d077"}, + {file = "orjson-3.11.6-cp314-cp314-win_arm64.whl", hash = "sha256:fe71f6b283f4f1832204ab8235ce07adad145052614f77c876fcf0dac97bc06f"}, + {file = "orjson-3.11.6.tar.gz", hash = "sha256:0a54c72259f35299fd033042367df781c2f66d10252955ca1efb7db309b954cb"}, ] [[package]] From ef08bb7831d90f43d99b5aa64faa382d6a8f9411 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 19 Mar 2026 14:21:07 +0100 Subject: [PATCH 212/296] feat(api): update API spec from langfuse/langfuse b3adfc3 (#1567) Co-authored-by: langfuse-bot --- langfuse/api/dataset_run_items/client.py | 10 ++++++++++ langfuse/api/dataset_run_items/raw_client.py | 10 ++++++++++ .../types/create_dataset_run_item_request.py | 7 +++++++ 3 files changed, 27 insertions(+) diff --git a/langfuse/api/dataset_run_items/client.py b/langfuse/api/dataset_run_items/client.py index cbafacc67..aee820da8 100644 --- a/langfuse/api/dataset_run_items/client.py +++ b/langfuse/api/dataset_run_items/client.py @@ -38,6 +38,7 @@ def create( observation_id: typing.Optional[str] = OMIT, trace_id: typing.Optional[str] = OMIT, dataset_version: typing.Optional[dt.datetime] = OMIT, + created_at: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> DatasetRunItem: """ @@ -66,6 +67,9 @@ def create( If provided, the experiment will use dataset items as they existed at or before this timestamp. If not provided, uses the latest version of dataset items. + created_at : typing.Optional[dt.datetime] + Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -98,6 +102,7 @@ def create( observation_id=observation_id, trace_id=trace_id, dataset_version=dataset_version, + created_at=created_at, request_options=request_options, ) return _response.data @@ -185,6 +190,7 @@ async def create( observation_id: typing.Optional[str] = OMIT, trace_id: typing.Optional[str] = OMIT, dataset_version: typing.Optional[dt.datetime] = OMIT, + created_at: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> DatasetRunItem: """ @@ -213,6 +219,9 @@ async def create( If provided, the experiment will use dataset items as they existed at or before this timestamp. If not provided, uses the latest version of dataset items. + created_at : typing.Optional[dt.datetime] + Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -253,6 +262,7 @@ async def main() -> None: observation_id=observation_id, trace_id=trace_id, dataset_version=dataset_version, + created_at=created_at, request_options=request_options, ) return _response.data diff --git a/langfuse/api/dataset_run_items/raw_client.py b/langfuse/api/dataset_run_items/raw_client.py index 894adbfff..f281b13d2 100644 --- a/langfuse/api/dataset_run_items/raw_client.py +++ b/langfuse/api/dataset_run_items/raw_client.py @@ -35,6 +35,7 @@ def create( observation_id: typing.Optional[str] = OMIT, trace_id: typing.Optional[str] = OMIT, dataset_version: typing.Optional[dt.datetime] = OMIT, + created_at: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[DatasetRunItem]: """ @@ -63,6 +64,9 @@ def create( If provided, the experiment will use dataset items as they existed at or before this timestamp. If not provided, uses the latest version of dataset items. + created_at : typing.Optional[dt.datetime] + Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -81,6 +85,7 @@ def create( "observationId": observation_id, "traceId": trace_id, "datasetVersion": dataset_version, + "createdAt": created_at, }, request_options=request_options, omit=OMIT, @@ -298,6 +303,7 @@ async def create( observation_id: typing.Optional[str] = OMIT, trace_id: typing.Optional[str] = OMIT, dataset_version: typing.Optional[dt.datetime] = OMIT, + created_at: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[DatasetRunItem]: """ @@ -326,6 +332,9 @@ async def create( If provided, the experiment will use dataset items as they existed at or before this timestamp. If not provided, uses the latest version of dataset items. + created_at : typing.Optional[dt.datetime] + Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -344,6 +353,7 @@ async def create( "observationId": observation_id, "traceId": trace_id, "datasetVersion": dataset_version, + "createdAt": created_at, }, request_options=request_options, omit=OMIT, diff --git a/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py b/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py index 43a586249..169888912 100644 --- a/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py +++ b/langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py @@ -46,6 +46,13 @@ class CreateDatasetRunItemRequest(UniversalBaseModel): If not provided, uses the latest version of dataset items. """ + created_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="createdAt") + ] = pydantic.Field(default=None) + """ + Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) From 21d79fc4115f6be372f5a023b3ba39c7407d83a0 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 19 Mar 2026 14:03:15 +0000 Subject: [PATCH 213/296] chore: release v4.0.1 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 9b67f697f..23a374f63 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "4.0.0" +__version__ = "4.0.1" diff --git a/pyproject.toml b/pyproject.toml index 4509f31c2..1ef6b5057 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "4.0.0" +version = "4.0.1" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 0bfd020a22abf84f819a7bf7cf61cc5b2e187a57 Mon Sep 17 00:00:00 2001 From: Zach Cloud <32704942+zachrobo1@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:58:40 -0400 Subject: [PATCH 214/296] fix(openai): correct token details field names for Response API usage (#1564) fix(openai): correct token details field names for Response API usage parsing The _parse_usage() function checked for input_token_details and output_token_details (singular), but OpenAI's Response API returns input_tokens_details and output_tokens_details (plural). This caused nested token detail fields (cached_tokens, reasoning_tokens) to be silently ignored. Co-authored-by: Claude Opus 4.6 Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/openai.py | 4 +- tests/test_parse_usage.py | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/test_parse_usage.py diff --git a/langfuse/openai.py b/langfuse/openai.py index 057d90cab..dbff11d37 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -539,8 +539,8 @@ def _parse_usage(usage: Optional[Any] = None) -> Any: for tokens_details in [ "prompt_tokens_details", "completion_tokens_details", - "input_token_details", - "output_token_details", + "input_tokens_details", + "output_tokens_details", ]: if tokens_details in usage_dict and usage_dict[tokens_details] is not None: tokens_details_dict = ( diff --git a/tests/test_parse_usage.py b/tests/test_parse_usage.py new file mode 100644 index 000000000..cfd6e4da4 --- /dev/null +++ b/tests/test_parse_usage.py @@ -0,0 +1,100 @@ +from langfuse.openai import _parse_usage + + +class TestParseUsageNone: + def test_returns_none_for_none(self): + assert _parse_usage(None) is None + + +class TestParseUsageEmbedding: + def test_embedding_usage_returns_input_only(self): + usage = {"prompt_tokens": 5, "total_tokens": 5} + result = _parse_usage(usage) + assert result == {"input": 5} + + +class TestParseUsageChatCompletions: + def test_prompt_tokens_details_flattened(self): + usage = { + "prompt_tokens": 100, + "completion_tokens": 50, + "total_tokens": 150, + "prompt_tokens_details": {"cached_tokens": 20, "audio_tokens": None}, + "completion_tokens_details": {"reasoning_tokens": 10}, + } + result = _parse_usage(usage) + assert result["prompt_tokens"] == 100 + assert result["completion_tokens"] == 50 + assert result["total_tokens"] == 150 + # None values are stripped, non-None kept + assert result["prompt_tokens_details"] == {"cached_tokens": 20} + assert result["completion_tokens_details"] == {"reasoning_tokens": 10} + + def test_details_as_object(self): + """Token details may arrive as an object with __dict__ instead of a dict.""" + + class Details: + def __init__(self): + self.cached_tokens = 30 + self.audio_tokens = None + + usage = { + "prompt_tokens": 100, + "completion_tokens": 50, + "total_tokens": 150, + "prompt_tokens_details": Details(), + "completion_tokens_details": None, + } + result = _parse_usage(usage) + assert result["prompt_tokens_details"] == {"cached_tokens": 30} + assert result["completion_tokens_details"] is None + + +class TestParseUsageResponseApi: + """Tests for OpenAI Response API usage format (input_tokens_details / output_tokens_details).""" + + def test_input_and_output_tokens_details_flattened(self): + usage = { + "input_tokens": 200, + "output_tokens": 80, + "total_tokens": 280, + "input_tokens_details": {"cached_tokens": 50}, + "output_tokens_details": {"reasoning_tokens": 30}, + } + result = _parse_usage(usage) + assert result["input_tokens"] == 200 + assert result["output_tokens"] == 80 + assert result["input_tokens_details"] == {"cached_tokens": 50} + assert result["output_tokens_details"] == {"reasoning_tokens": 30} + + def test_none_values_stripped_from_details(self): + usage = { + "input_tokens": 200, + "output_tokens": 80, + "total_tokens": 280, + "input_tokens_details": {"cached_tokens": 50, "audio_tokens": None}, + "output_tokens_details": {"reasoning_tokens": None}, + } + result = _parse_usage(usage) + assert result["input_tokens_details"] == {"cached_tokens": 50} + assert result["output_tokens_details"] == {} + + def test_details_as_object(self): + class InputDetails: + def __init__(self): + self.cached_tokens = 40 + + class OutputDetails: + def __init__(self): + self.reasoning_tokens = 15 + + usage = { + "input_tokens": 100, + "output_tokens": 50, + "total_tokens": 150, + "input_tokens_details": InputDetails(), + "output_tokens_details": OutputDetails(), + } + result = _parse_usage(usage) + assert result["input_tokens_details"] == {"cached_tokens": 40} + assert result["output_tokens_details"] == {"reasoning_tokens": 15} From abe37598c78ea10c30a0685b2093d15d11de8ebb Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 19 Mar 2026 17:59:36 +0100 Subject: [PATCH 215/296] chore: remove tests/test_parse_usage.py --- tests/test_parse_usage.py | 100 -------------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 tests/test_parse_usage.py diff --git a/tests/test_parse_usage.py b/tests/test_parse_usage.py deleted file mode 100644 index cfd6e4da4..000000000 --- a/tests/test_parse_usage.py +++ /dev/null @@ -1,100 +0,0 @@ -from langfuse.openai import _parse_usage - - -class TestParseUsageNone: - def test_returns_none_for_none(self): - assert _parse_usage(None) is None - - -class TestParseUsageEmbedding: - def test_embedding_usage_returns_input_only(self): - usage = {"prompt_tokens": 5, "total_tokens": 5} - result = _parse_usage(usage) - assert result == {"input": 5} - - -class TestParseUsageChatCompletions: - def test_prompt_tokens_details_flattened(self): - usage = { - "prompt_tokens": 100, - "completion_tokens": 50, - "total_tokens": 150, - "prompt_tokens_details": {"cached_tokens": 20, "audio_tokens": None}, - "completion_tokens_details": {"reasoning_tokens": 10}, - } - result = _parse_usage(usage) - assert result["prompt_tokens"] == 100 - assert result["completion_tokens"] == 50 - assert result["total_tokens"] == 150 - # None values are stripped, non-None kept - assert result["prompt_tokens_details"] == {"cached_tokens": 20} - assert result["completion_tokens_details"] == {"reasoning_tokens": 10} - - def test_details_as_object(self): - """Token details may arrive as an object with __dict__ instead of a dict.""" - - class Details: - def __init__(self): - self.cached_tokens = 30 - self.audio_tokens = None - - usage = { - "prompt_tokens": 100, - "completion_tokens": 50, - "total_tokens": 150, - "prompt_tokens_details": Details(), - "completion_tokens_details": None, - } - result = _parse_usage(usage) - assert result["prompt_tokens_details"] == {"cached_tokens": 30} - assert result["completion_tokens_details"] is None - - -class TestParseUsageResponseApi: - """Tests for OpenAI Response API usage format (input_tokens_details / output_tokens_details).""" - - def test_input_and_output_tokens_details_flattened(self): - usage = { - "input_tokens": 200, - "output_tokens": 80, - "total_tokens": 280, - "input_tokens_details": {"cached_tokens": 50}, - "output_tokens_details": {"reasoning_tokens": 30}, - } - result = _parse_usage(usage) - assert result["input_tokens"] == 200 - assert result["output_tokens"] == 80 - assert result["input_tokens_details"] == {"cached_tokens": 50} - assert result["output_tokens_details"] == {"reasoning_tokens": 30} - - def test_none_values_stripped_from_details(self): - usage = { - "input_tokens": 200, - "output_tokens": 80, - "total_tokens": 280, - "input_tokens_details": {"cached_tokens": 50, "audio_tokens": None}, - "output_tokens_details": {"reasoning_tokens": None}, - } - result = _parse_usage(usage) - assert result["input_tokens_details"] == {"cached_tokens": 50} - assert result["output_tokens_details"] == {} - - def test_details_as_object(self): - class InputDetails: - def __init__(self): - self.cached_tokens = 40 - - class OutputDetails: - def __init__(self): - self.reasoning_tokens = 15 - - usage = { - "input_tokens": 100, - "output_tokens": 50, - "total_tokens": 150, - "input_tokens_details": InputDetails(), - "output_tokens_details": OutputDetails(), - } - result = _parse_usage(usage) - assert result["input_tokens_details"] == {"cached_tokens": 40} - assert result["output_tokens_details"] == {"reasoning_tokens": 15} From cf68968fd55ba1ef043f6310d82116c297951389 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:01:10 +0100 Subject: [PATCH 216/296] chore: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94dc1c1d3..6eb845a75 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## Installation > [!IMPORTANT] -> The SDK was rewritten in v3 and released in June 2025. Refer to the [v3 migration guide](https://langfuse.com/docs/sdk/python/sdk-v3#upgrade-from-v2) for instructions on updating your code. +> The SDK was rewritten in v4 and released in March 2026. Refer to the [v4 migration guide](https://langfuse.com/docs/observability/sdk/upgrade-path/python-v3-to-v4) for instructions on updating your code. ``` pip install langfuse From 51e0b7fd46629afc346d2972067ec91f0f87e477 Mon Sep 17 00:00:00 2001 From: DJoeyG Date: Thu, 19 Mar 2026 17:11:51 +0000 Subject: [PATCH 217/296] fix(openai): include responses instructions in captured prompt (#1565) Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/openai.py | 30 ++++++++++++++++- tests/test_openai.py | 5 ++- tests/test_openai_prompt_extraction.py | 46 ++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 tests/test_openai_prompt_extraction.py diff --git a/langfuse/openai.py b/langfuse/openai.py index dbff11d37..16d293e73 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -246,6 +246,34 @@ def wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any: return _with_langfuse +def _extract_responses_prompt(kwargs: Any) -> Any: + input_value = kwargs.get("input", None) + instructions = kwargs.get("instructions", None) + + if isinstance(input_value, NotGiven): + input_value = None + + if isinstance(instructions, NotGiven): + instructions = None + + if instructions is None: + return input_value + + if input_value is None: + return {"instructions": instructions} + + if isinstance(input_value, str): + return [ + {"role": "system", "content": instructions}, + {"role": "user", "content": input_value}, + ] + + if isinstance(input_value, list): + return [{"role": "system", "content": instructions}, *input_value] + + return {"instructions": instructions, "input": input_value} + + def _extract_chat_prompt(kwargs: Any) -> Any: """Extracts the user input from prompts. Returns an array of messages or dict with messages and functions""" prompt = {} @@ -403,7 +431,7 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A if resource.type == "completion": prompt = kwargs.get("prompt", None) elif resource.object == "Responses" or resource.object == "AsyncResponses": - prompt = kwargs.get("input", None) + prompt = _extract_responses_prompt(kwargs) elif resource.type == "chat": prompt = _extract_chat_prompt(kwargs) elif resource.type == "embedding": diff --git a/tests/test_openai.py b/tests/test_openai.py index 8706bae13..47f17a5c8 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -1407,7 +1407,10 @@ def test_response_api_streaming(openai): assert len(generation.data) != 0 generationData = generation.data[0] assert generationData.name == generation_name - assert generation.data[0].input == "Hello!" + assert generation.data[0].input == [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"}, + ] assert generationData.type == "GENERATION" assert "gpt-4o" in generationData.model assert generationData.start_time is not None diff --git a/tests/test_openai_prompt_extraction.py b/tests/test_openai_prompt_extraction.py new file mode 100644 index 000000000..9d1ab02cc --- /dev/null +++ b/tests/test_openai_prompt_extraction.py @@ -0,0 +1,46 @@ +import pytest + +try: + # Compatibility across OpenAI SDK versions where NOT_GIVEN export moved. + from openai import NOT_GIVEN +except ImportError: + from openai._types import NOT_GIVEN + +from langfuse.openai import _extract_responses_prompt + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"input": "Hello!"}, "Hello!"), + ( + {"instructions": "You are helpful.", "input": "Hello!"}, + [ + {"role": "system", "content": "You are helpful."}, + {"role": "user", "content": "Hello!"}, + ], + ), + ( + { + "instructions": "You are helpful.", + "input": [{"role": "user", "content": "Hello!"}], + }, + [ + {"role": "system", "content": "You are helpful."}, + {"role": "user", "content": "Hello!"}, + ], + ), + ( + {"instructions": "You are helpful."}, + {"instructions": "You are helpful."}, + ), + ( + {"instructions": "You are helpful.", "input": NOT_GIVEN}, + {"instructions": "You are helpful."}, + ), + ({"instructions": NOT_GIVEN, "input": "Hello!"}, "Hello!"), + ({"instructions": NOT_GIVEN, "input": NOT_GIVEN}, None), + ], +) +def test_extract_responses_prompt(kwargs, expected): + assert _extract_responses_prompt(kwargs) == expected From 1806084e521773ede6bde2ca1732f66f38c2bdf1 Mon Sep 17 00:00:00 2001 From: Jinoh Kang Date: Fri, 20 Mar 2026 02:32:56 +0900 Subject: [PATCH 218/296] fix(observe): Handle asyncio.CancelledError in exception blocks (#1566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes langfuse/langfuse#12650 This catches and records the async task being cancelled—either directly or because it timed out—which will raise an asyncio.CancelledError to stop execution. Also fix sync wrappers as well. Note that asyncio.CancelledError can cross async-sync boundaries, such as `run_coroutine_threadsafe().result()` or a sync helper function that re-raises cancellation that was previously suppressed/deferred. Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/_client/observe.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index 6252c5383..c648a0a62 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -327,7 +327,7 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: langfuse_span_or_generation.update(output=result) return result - except Exception as e: + except (Exception, asyncio.CancelledError) as e: langfuse_span_or_generation.update( level="ERROR", status_message=str(e) or type(e).__name__ ) @@ -445,7 +445,7 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: langfuse_span_or_generation.update(output=result) return result - except Exception as e: + except (Exception, asyncio.CancelledError) as e: langfuse_span_or_generation.update( level="ERROR", status_message=str(e) or type(e).__name__ ) @@ -586,7 +586,7 @@ def __next__(self) -> Any: raise # Re-raise StopIteration - except Exception as e: + except (Exception, asyncio.CancelledError) as e: self.span.update( level="ERROR", status_message=str(e) or type(e).__name__ ).end() @@ -653,7 +653,7 @@ async def __anext__(self) -> Any: self.span.update(output=output).end() raise # Re-raise StopAsyncIteration - except Exception as e: + except (Exception, asyncio.CancelledError) as e: self.span.update( level="ERROR", status_message=str(e) or type(e).__name__ ).end() From 900668393d09ef59c93aca1a8b82572276be52bf Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:45:01 +0100 Subject: [PATCH 219/296] chore: relax packaging constraint to <27 (#1570) Co-authored-by: @noirbee --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6344850d6..1d91e08dd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -650,7 +650,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -2894,9 +2894,9 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "1153d68c8977db835b7425287bacb02a827f6d1a4a259ae6bacf010d2b91bc94" +content-hash = "fb12d670f161c15a324be9497a9dd141dce0f4262ce1bedd1949cb45ef86cc51" diff --git a/pyproject.toml b/pyproject.toml index 1ef6b5057..b5f7d0d75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ pydantic = "^2" backoff = ">=1.10.0" openai = { version = ">=0.27.8", optional = true } wrapt = "^1.14" -packaging = ">=23.2,<26.0" +packaging = ">=23.2,<27.0" opentelemetry-api = "^1.33.1" opentelemetry-sdk = "^1.33.1" opentelemetry-exporter-otlp-proto-http = "^1.33.1" From d935f023241d34e09b9cf4f6800392e7c47d86a7 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:48:15 +0100 Subject: [PATCH 220/296] chore: add claude review comment action --- .../claude-review-maintainer-prs.yml | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/claude-review-maintainer-prs.yml diff --git a/.github/workflows/claude-review-maintainer-prs.yml b/.github/workflows/claude-review-maintainer-prs.yml new file mode 100644 index 000000000..016559888 --- /dev/null +++ b/.github/workflows/claude-review-maintainer-prs.yml @@ -0,0 +1,68 @@ +name: Claude Review on Maintainer PRs + +on: + pull_request_target: + types: + - opened + - ready_for_review + +jobs: + comment: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Check author permission and existing review request + id: check + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.payload.pull_request.number; + const username = context.payload.pull_request.user.login; + + let permission = "none"; + try { + const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username, + }); + permission = data.permission; + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + + const canWrite = ["write", "admin"].includes(permission); + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + per_page: 100, + }); + const hasReviewRequest = comments.some( + (comment) => comment.body?.trim() === "@claude review", + ); + + core.info( + `PR #${issue_number} by ${username}: permission=${permission}, hasReviewRequest=${hasReviewRequest}`, + ); + + core.setOutput("should_comment", canWrite && !hasReviewRequest ? "true" : "false"); + + - name: Add Claude review comment + if: steps.check.outputs.should_comment == 'true' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: "@claude review", + }); From c5dc24d4479c789fa6ba2a22ffdf0df7d9280b3d Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 25 Mar 2026 15:48:37 +0100 Subject: [PATCH 221/296] feat(api): update API spec from langfuse/langfuse a6c38c6 (#1574) Co-authored-by: langfuse-bot --- langfuse/api/blob_storage_integrations/client.py | 10 ++++++++++ langfuse/api/blob_storage_integrations/raw_client.py | 10 ++++++++++ .../types/blob_storage_integration_response.py | 1 + .../types/create_blob_storage_integration_request.py | 5 +++++ 4 files changed, 26 insertions(+) diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index c79bc82c2..a8d693cec 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -94,6 +94,7 @@ def upsert_blob_storage_integration( secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, + compressed: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> BlobStorageIntegrationResponse: """ @@ -139,6 +140,9 @@ def upsert_blob_storage_integration( export_start_date : typing.Optional[dt.datetime] Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + compressed : typing.Optional[bool] + Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -191,6 +195,7 @@ def upsert_blob_storage_integration( secret_access_key=secret_access_key, prefix=prefix, export_start_date=export_start_date, + compressed=compressed, request_options=request_options, ) return _response.data @@ -348,6 +353,7 @@ async def upsert_blob_storage_integration( secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, + compressed: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> BlobStorageIntegrationResponse: """ @@ -393,6 +399,9 @@ async def upsert_blob_storage_integration( export_start_date : typing.Optional[dt.datetime] Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + compressed : typing.Optional[bool] + Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -453,6 +462,7 @@ async def main() -> None: secret_access_key=secret_access_key, prefix=prefix, export_start_date=export_start_date, + compressed=compressed, request_options=request_options, ) return _response.data diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 69143f4d6..30e486209 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -151,6 +151,7 @@ def upsert_blob_storage_integration( secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, + compressed: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[BlobStorageIntegrationResponse]: """ @@ -196,6 +197,9 @@ def upsert_blob_storage_integration( export_start_date : typing.Optional[dt.datetime] Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + compressed : typing.Optional[bool] + Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -221,6 +225,7 @@ def upsert_blob_storage_integration( "fileType": file_type, "exportMode": export_mode, "exportStartDate": export_start_date, + "compressed": compressed, }, request_options=request_options, omit=OMIT, @@ -623,6 +628,7 @@ async def upsert_blob_storage_integration( secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, + compressed: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[BlobStorageIntegrationResponse]: """ @@ -668,6 +674,9 @@ async def upsert_blob_storage_integration( export_start_date : typing.Optional[dt.datetime] Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + compressed : typing.Optional[bool] + Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -693,6 +702,7 @@ async def upsert_blob_storage_integration( "fileType": file_type, "exportMode": export_mode, "exportStartDate": export_start_date, + "compressed": compressed, }, request_options=request_options, omit=OMIT, diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py index c0536f14e..b3630297b 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py @@ -40,6 +40,7 @@ class BlobStorageIntegrationResponse(UniversalBaseModel): export_start_date: typing_extensions.Annotated[ typing.Optional[dt.datetime], FieldMetadata(alias="exportStartDate") ] = None + compressed: bool next_sync_at: typing_extensions.Annotated[ typing.Optional[dt.datetime], FieldMetadata(alias="nextSyncAt") ] = None diff --git a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py index 3a4d9fa06..ce23cd246 100644 --- a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +++ b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py @@ -86,6 +86,11 @@ class CreateBlobStorageIntegrationRequest(UniversalBaseModel): Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) """ + compressed: typing.Optional[bool] = pydantic.Field(default=None) + """ + Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) From 92a318851e7b61210cbf1472ef23237d3c0e8f14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:14:35 +0100 Subject: [PATCH 222/296] chore(deps): bump requests from 2.32.5 to 2.33.0 (#1579) Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0) --- updated-dependencies: - dependency-name: requests dependency-version: 2.33.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1d91e08dd..95e22a698 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -650,7 +650,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.3.6" +jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -2001,25 +2001,26 @@ files = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, + {file = "requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b"}, + {file = "requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652"}, ] [package.dependencies] -certifi = ">=2017.4.17" +certifi = ">=2023.5.7" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +test = ["PySocks (>=1.5.6,!=1.5.7)", "pytest (>=3)", "pytest-cov", "pytest-httpbin (==2.1.0)", "pytest-mock", "pytest-xdist"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "requests-toolbelt" @@ -2894,7 +2895,7 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [metadata] lock-version = "2.1" From 0ec542e4ddb6baada52e5394d1cd5143380ca2fa Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:57:12 +0100 Subject: [PATCH 223/296] fix(scores): parse session ID correctly (#1582) --- .github/workflows/ci.yml | 2 +- CLAUDE.md | 9 ++++++ langfuse/_client/client.py | 2 +- langfuse/_utils/request.py | 3 +- langfuse/langchain/CallbackHandler.py | 6 ++-- tests/test_core_sdk.py | 43 +++++++++++++++++++++++++++ 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 113733a84..2bdf4e7b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v3 with: - version: 9.5.0 + version: 10.33.0 - name: Clone langfuse server run: | diff --git a/CLAUDE.md b/CLAUDE.md index 6dc8afbb2..51f3eaeeb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ This is the Langfuse Python SDK, a client library for accessing the Langfuse obs ## Development Commands ### Setup + ```bash # Install Poetry plugins (one-time setup) poetry self add poetry-dotenv-plugin @@ -21,6 +22,7 @@ poetry run pre-commit install ``` ### Testing + ```bash # Run all tests with verbose output poetry run pytest -s -v --log-cli-level=INFO @@ -33,6 +35,7 @@ poetry run pytest -s -v --log-cli-level=INFO -n auto ``` ### Code Quality + ```bash # Format code with Ruff poetry run ruff format . @@ -48,6 +51,7 @@ poetry run pre-commit run --all-files ``` ### Building and Releasing + ```bash # Build the package locally (for testing) poetry build @@ -57,6 +61,7 @@ poetry run pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfus ``` Releases are automated via GitHub Actions. To release: + 1. Go to Actions > "Release Python SDK" workflow 2. Click "Run workflow" 3. Select version bump type (patch/minor/major/prerelease) @@ -89,6 +94,7 @@ The workflow handles versioning, building, PyPI publishing (via OIDC), and GitHu ### Key Design Patterns The SDK is built on OpenTelemetry for observability, using: + - Spans for tracing LLM operations - Attributes for metadata (see `LangfuseOtelSpanAttributes`) - Resource management for efficient batching and flushing @@ -98,6 +104,7 @@ The client follows an async-first design with automatic batching of events and b ## Configuration Environment variables (defined in `_client/environment_variables.py`): + - `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY`: API credentials - `LANGFUSE_HOST`: API endpoint (defaults to https://cloud.langfuse.com) - `LANGFUSE_DEBUG`: Enable debug logging @@ -127,9 +134,11 @@ The `langfuse/api/` directory is auto-generated from the Langfuse OpenAPI specif ## Testing Guidelines ### Approach to Test Changes + - Don't remove functionality from existing unit tests just to make tests pass. Only change the test, if underlying code changes warrant a test change. ## Python Code Rules ### Exception Handling + - Exception must not use an f-string literal, assign to variable first diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index a8781c9b9..9beed5f67 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -1818,7 +1818,7 @@ def create_score( try: new_body = ScoreBody( id=score_id, - session_id=session_id, + sessionId=session_id, datasetRunId=dataset_run_id, traceId=trace_id, observationId=observation_id, diff --git a/langfuse/_utils/request.py b/langfuse/_utils/request.py index 104552ee7..402d0b5a7 100644 --- a/langfuse/_utils/request.py +++ b/langfuse/_utils/request.py @@ -48,9 +48,8 @@ def generate_headers(self) -> dict: def batch_post(self, **kwargs: Any) -> httpx.Response: """Post the `kwargs` to the batch API endpoint for events""" - logger.debug("uploading data: %s", kwargs) - res = self.post(**kwargs) + return self._process_response( res, success_message="data uploaded successfully", return_json=False ) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 0cd4dd133..373461c51 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1057,10 +1057,10 @@ def _convert_message_to_dict(self, message: BaseMessage) -> Dict[str, Any]: and len(message.tool_calls) > 0 ): message_dict["tool_calls"] = message.tool_calls - + if ( - hasattr(message, "invalid_tool_calls") - and message.invalid_tool_calls is not None + hasattr(message, "invalid_tool_calls") + and message.invalid_tool_calls is not None and len(message.invalid_tool_calls) > 0 ): message_dict["invalid_tool_calls"] = message.invalid_tool_calls diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 10a6b0d80..91064de23 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -118,6 +118,49 @@ def test_invalid_score_data_does_not_raise_exception(): # We can't assert queue size in OTEL implementation, but we can verify it completes without exception +def test_create_session_score(): + langfuse = Langfuse() + + session_id = "my-session" + + # Create a span and set trace properties + with langfuse.start_as_current_observation(name="test-span"): + with propagate_attributes( + trace_name="this-is-so-great-new", + user_id="test", + metadata={"test": "test"}, + session_id=session_id, + ): + pass + + # Ensure data is sent + langfuse.flush() + sleep(2) + + # Create a numeric score + score_id = create_uuid() + + langfuse.create_score( + score_id=score_id, + session_id=session_id, + name="this-is-a-score", + value=1, + ) + + # Ensure data is sent + langfuse.flush() + sleep(2) + + # Retrieve and verify + score = langfuse.api.scores.get_by_id(score_id) + + # find the score by name (server may transform the id format) + assert score is not None + assert score.value == 1 + assert score.data_type == "NUMERIC" + assert score.session_id == session_id + + def test_create_numeric_score(): langfuse = Langfuse() api_wrapper = LangfuseAPI() From 840cf2a94bb48770a3f256742f946137c2f59392 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:15:36 +0100 Subject: [PATCH 224/296] chore: fix flaky langchain tests (#1584) * push * push --- tests/test_langchain.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 575cbf150..6c9d3eb4d 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -51,7 +51,7 @@ def test_callback_generated_from_trace_chat(): assert trace.id == trace_id - assert len(trace.observations) == 2 + assert len(trace.observations) == 3 langchain_generation_span = list( filter( @@ -286,7 +286,7 @@ def test_openai_instruct_usage(): observations = get_api().trace.get(trace_id).observations # Add 1 to account for the wrapping span - assert len(observations) == 3 + assert len(observations) == 4 for observation in observations: if observation.type == "GENERATION": @@ -391,6 +391,7 @@ def test_get_langchain_chat_prompt(): ) +@pytest.mark.skip("Flaky") def test_link_langfuse_prompts_invoke(): langfuse = Langfuse() trace_name = "test_link_langfuse_prompts_invoke" @@ -463,7 +464,7 @@ def test_link_langfuse_prompts_invoke(): key=lambda x: x.start_time, ) - assert len(generations) == 2 + # assert len(generations) == 4 assert generations[0].input == "Tell me a joke involving the animal dog" assert "Explain the joke to me like I'm a 5 year old" in generations[1].input @@ -474,6 +475,7 @@ def test_link_langfuse_prompts_invoke(): assert generations[1].prompt_version == langfuse_explain_prompt.version +@pytest.mark.skip("Flaky") def test_link_langfuse_prompts_stream(): langfuse = Langfuse() trace_name = "test_link_langfuse_prompts_stream" @@ -550,7 +552,7 @@ def test_link_langfuse_prompts_stream(): key=lambda x: x.start_time, ) - assert len(generations) == 2 + assert len(generations) == 4 assert generations[0].input == "Tell me a joke involving the animal dog" assert "Explain the joke to me like I'm a 5 year old" in generations[1].input @@ -564,6 +566,7 @@ def test_link_langfuse_prompts_stream(): assert generations[1].time_to_first_token is not None +@pytest.mark.skip("Flaky") def test_link_langfuse_prompts_batch(): langfuse = Langfuse() trace_name = "test_link_langfuse_prompts_batch_" + create_uuid()[:8] @@ -639,7 +642,7 @@ def test_link_langfuse_prompts_batch(): key=lambda x: x.start_time, ) - assert len(generations) == 6 + assert len(generations) == 10 assert generations[0].prompt_name == joke_prompt_name assert generations[1].prompt_name == joke_prompt_name @@ -710,6 +713,7 @@ def test_get_langchain_chat_prompt_with_precompiled_prompt(): assert user_message.content == "This is a langchain chain." +@pytest.mark.skip("Flaky") def test_callback_openai_functions_with_tools(): handler = CallbackHandler() @@ -856,7 +860,7 @@ def test_multimodal(): trace = get_api().trace.get(trace_id=trace_id) - assert len(trace.observations) == 2 + assert len(trace.observations) == 3 # Filter for the observation with type GENERATION generation_observation = next( (obs for obs in trace.observations if obs.type == "GENERATION"), None From df702da42a03de35c1c665f7dec7ff9cc3f482f5 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:04:29 +0200 Subject: [PATCH 225/296] fix(experiments): maintain propagated context in async experiments (#1587) --- langfuse/_client/utils.py | 4 +++- tests/test_propagate_attributes.py | 30 ++++++++++++++++++++++++++++++ tests/test_utils.py | 17 +++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/langfuse/_client/utils.py b/langfuse/_client/utils.py index 16d963d88..ccce3d9ef 100644 --- a/langfuse/_client/utils.py +++ b/langfuse/_client/utils.py @@ -5,6 +5,7 @@ """ import asyncio +import contextvars import json import threading from hashlib import sha256 @@ -69,13 +70,14 @@ class _RunAsyncThread(threading.Thread): def __init__(self, coro: Coroutine[Any, Any, Any]) -> None: self.coro = coro + self.context = contextvars.copy_context() self.result: Any = None self.exception: Exception | None = None super().__init__() def run(self) -> None: try: - self.result = asyncio.run(self.coro) + self.result = self.context.run(asyncio.run, self.coro) except Exception as e: self.exception = e diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py index 566ba6392..b3be9f830 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/test_propagate_attributes.py @@ -2295,6 +2295,36 @@ def test_tags_attribute_key_format(self, langfuse_client, memory_exporter): class TestPropagateAttributesExperiment(TestPropagateAttributesBase): """Tests for experiment attribute propagation.""" + @pytest.mark.asyncio + async def test_experiment_propagates_user_id_in_async_context( + self, langfuse_client, memory_exporter + ): + """Verify run_experiment keeps propagated attributes when called from async code.""" + import asyncio + + local_data = [{"input": "test input", "expected_output": "expected output"}] + + async def async_task(*, item, **kwargs): + await asyncio.sleep(0.01) + return f"processed: {item['input']}" + + with propagate_attributes(user_id="async-experiment-user"): + langfuse_client.run_experiment( + name="Async Experiment", + data=local_data, + task=async_task, + ) + + langfuse_client.flush() + time.sleep(0.1) + + root_span = self.get_span_by_name(memory_exporter, "experiment-item-run") + self.verify_span_attribute( + root_span, + LangfuseOtelSpanAttributes.TRACE_USER_ID, + "async-experiment-user", + ) + def test_experiment_attributes_propagate_without_dataset( self, langfuse_client, memory_exporter ): diff --git a/tests/test_utils.py b/tests/test_utils.py index ac3ee8473..968bcb91b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,7 @@ """Test suite for utility functions in langfuse._client.utils module.""" import asyncio +import contextvars import threading from unittest import mock @@ -81,6 +82,22 @@ async def check_thread_isolation(): result = run_async_safely(check_thread_isolation()) assert result == "isolated" + @pytest.mark.asyncio + async def test_run_async_context_preserves_contextvars(self): + """Test that threaded execution preserves the caller's contextvars.""" + request_id = contextvars.ContextVar("request_id") + token = request_id.set("req-123") + + async def read_contextvar(): + await asyncio.sleep(0.001) + return request_id.get() + + try: + result = run_async_safely(read_contextvar()) + assert result == "req-123" + finally: + request_id.reset(token) + def test_multiple_calls_sync_context(self): """Test multiple sequential calls in sync context.""" From 395bc8f2974f581d7ad1bb2deaddd1a0e028682e Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 30 Mar 2026 12:05:47 +0000 Subject: [PATCH 226/296] chore: release v4.0.2 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 23a374f63..5808d2e24 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "4.0.1" +__version__ = "4.0.2" diff --git a/pyproject.toml b/pyproject.toml index b5f7d0d75..11eacd192 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "4.0.1" +version = "4.0.2" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 406e10a40b07d609ad939f5538874afc8a6e74a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:55:06 +0200 Subject: [PATCH 227/296] chore(deps-dev): bump langchain-core from 1.2.11 to 1.2.22 (#1586) Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 1.2.11 to 1.2.22. - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.11...langchain-core==1.2.22) --- updated-dependencies: - dependency-name: langchain-core dependency-version: 1.2.22 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 95e22a698..4f95c4caa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -709,14 +709,14 @@ xai = ["langchain-xai"] [[package]] name = "langchain-core" -version = "1.2.11" +version = "1.2.22" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["dev"] files = [ - {file = "langchain_core-1.2.11-py3-none-any.whl", hash = "sha256:ae11ceb8dda60d0b9d09e763116e592f1683327c17be5b715f350fd29aee65d3"}, - {file = "langchain_core-1.2.11.tar.gz", hash = "sha256:f164bb36602dd74a3a50c1334fca75309ad5ed95767acdfdbb9fa95ce28a1e01"}, + {file = "langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b"}, + {file = "langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058"}, ] [package.dependencies] From 549facadfa3cfc6882e0d3fbfae74599387876e3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:14:50 +0200 Subject: [PATCH 228/296] fix(langchain): exit propagation context gracefully (#1588) --- langfuse/_client/propagation.py | 23 ++++++++++++-- langfuse/langchain/CallbackHandler.py | 43 +++++++++++++++------------ 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index e0c68db00..2de9cefb9 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -7,7 +7,9 @@ from typing import Any, Dict, Generator, List, Literal, Optional, TypedDict, Union, cast -from opentelemetry import baggage +from opentelemetry import ( + baggage, +) from opentelemetry import ( baggage as otel_baggage_api, ) @@ -17,6 +19,7 @@ from opentelemetry import ( trace as otel_trace_api, ) +from opentelemetry.context import _RUNTIME_CONTEXT from opentelemetry.util._decorator import ( _AgnosticContextManager, _agnosticcontextmanager, @@ -72,6 +75,22 @@ class PropagatedExperimentAttributes(TypedDict): experiment_item_root_observation_id: str +def _detach_context_token_safely(token: Any) -> None: + """Detach a context token without emitting noisy async teardown errors. + + OpenTelemetry tokens are backed by ``contextvars`` and must be detached in the + same execution context where they were attached. Async frameworks can legitimately + end spans or unwind context managers in a different task/context, in which case + detach raises and the public OpenTelemetry helper logs an error. At that point the + observation is already completed, so the mismatch is safe to ignore. + """ + + try: + _RUNTIME_CONTEXT.detach(token) + except Exception: + pass + + def propagate_attributes( *, user_id: Optional[str] = None, @@ -272,7 +291,7 @@ def _propagate_attributes( yield finally: - otel_context_api.detach(token) + _detach_context_token_safely(token) def _get_propagated_attributes_from_context( diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 373461c51..5b5dfe691 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -15,13 +15,13 @@ import pydantic from opentelemetry import context, trace -from opentelemetry.context import _RUNTIME_CONTEXT from opentelemetry.util._decorator import _AgnosticContextManager from langfuse import propagate_attributes from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse._client.client import Langfuse from langfuse._client.get_client import get_client +from langfuse._client.propagation import _detach_context_token_safely from langfuse._client.span import ( LangfuseAgent, LangfuseChain, @@ -458,18 +458,7 @@ def _detach_observation( token = self._context_tokens.pop(run_id, None) if token: - try: - # Directly detach from runtime context to avoid error logging - _RUNTIME_CONTEXT.detach(token) - except Exception: - # Context detach can fail in async scenarios - this is expected and safe to ignore - # The span itself was properly ended and tracing data is correctly captured - # - # Examples: - # 1. Token created in one async task/thread, detached in another - # 2. Context already detached by framework or other handlers - # 3. Runtime context state mismatch in concurrent execution - pass + _detach_context_token_safely(token) return cast( Union[ @@ -564,11 +553,8 @@ def on_chain_end( input=kwargs.get("inputs"), ) - if ( - parent_run_id is None - and self._propagation_context_manager is not None - ): - self._propagation_context_manager.__exit__(None, None, None) + if parent_run_id is None: + self._exit_propagation_context() span.end() @@ -579,6 +565,7 @@ def on_chain_end( finally: if parent_run_id is None: + self._exit_propagation_context() self._reset() def on_chain_error( @@ -608,10 +595,19 @@ def on_chain_error( status_message=str(error) if level else None, input=kwargs.get("inputs"), cost_details={"total": 0}, - ).end() + ) + + if parent_run_id is None: + self._exit_propagation_context() + + observation.end() except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._exit_propagation_context() + self._reset() def on_chat_model_start( self, @@ -1026,6 +1022,15 @@ def on_llm_error( def _reset(self) -> None: self._child_to_parent_run_id_map = {} + def _exit_propagation_context(self) -> None: + manager = self._propagation_context_manager + + if manager is None: + return + + self._propagation_context_manager = None + manager.__exit__(None, None, None) + def __join_tags_and_metadata( self, tags: Optional[List[str]] = None, From 76197310da9c1f06f5d4249c8df94d32fa8ab628 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 30 Mar 2026 14:18:19 +0000 Subject: [PATCH 229/296] chore: release v4.0.3 --- langfuse/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/version.py b/langfuse/version.py index 5808d2e24..a1bd9274f 100644 --- a/langfuse/version.py +++ b/langfuse/version.py @@ -1,3 +1,3 @@ """@private""" -__version__ = "4.0.2" +__version__ = "4.0.3" diff --git a/pyproject.toml b/pyproject.toml index 11eacd192..e564e59bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "langfuse" -version = "4.0.2" +version = "4.0.3" description = "A client library for accessing langfuse" authors = ["langfuse "] license = "MIT" From 31d513d706233a9cd63ad443fef5f9562b68cbe3 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:39:57 +0200 Subject: [PATCH 230/296] chore: migrate repo from poetry to uv (#1589) --- .github/dependabot.yml | 2 +- .github/workflows/ci.yml | 66 +- .github/workflows/release.yml | 49 +- .pre-commit-config.yaml | 47 +- CLAUDE.md | 35 +- CONTRIBUTING.md | 32 +- langfuse/__init__.py | 2 + langfuse/_client/resource_manager.py | 2 +- langfuse/_client/span_processor.py | 2 +- .../_task_manager/score_ingestion_consumer.py | 2 +- langfuse/_version.py | 13 + langfuse/version.py | 3 - poetry.lock | 2903 ----------------- pyproject.toml | 75 +- tests/test_version.py | 7 + uv.lock | 2450 ++++++++++++++ 16 files changed, 2605 insertions(+), 3085 deletions(-) create mode 100644 langfuse/_version.py delete mode 100644 langfuse/version.py delete mode 100644 poetry.lock create mode 100644 tests/test_version.py create mode 100644 uv.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 04b490b64..f949dfb74 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "pip" # See documentation for possible values + - package-ecosystem: "uv" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bdf4e7b0..68c3dc977 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,27 +19,27 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: astral-sh/ruff-action@v3 + - name: Install uv and set Python version + uses: astral-sh/setup-uv@v7 + with: + version: "0.11.2" + python-version: "3.13" + enable-cache: true + - name: Install dependencies + run: uv sync --locked + - name: Run Ruff + run: uv run --frozen ruff check . type-checking: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 + - name: Install uv and set Python version + uses: astral-sh/setup-uv@v7 with: + version: "0.11.2" python-version: "3.13" - - name: Install poetry - uses: abatilo/actions-poetry@v2 - - name: Setup a local virtual environment - run: | - poetry config virtualenvs.create true --local - poetry config virtualenvs.in-project true --local - - uses: actions/cache@v3 - name: Define a cache for the virtual environment based on the dependencies lock file - with: - path: ./.venv - key: venv-type-check-${{ hashFiles('poetry.lock') }} + enable-cache: true - uses: actions/cache@v3 name: Cache mypy cache with: @@ -48,9 +48,9 @@ jobs: restore-keys: | mypy- - name: Install dependencies - run: poetry install --only=main,dev + run: uv sync --locked - name: Run mypy type checking - run: poetry run mypy langfuse --no-error-summary + run: uv run --frozen mypy langfuse --no-error-summary ci: runs-on: ubuntu-latest @@ -154,43 +154,23 @@ jobs: done echo "Langfuse server is up and running!" - - name: Install Python - uses: actions/setup-python@v4 - # see details (matrix, python-version, python-version-file, etc.) - # https://github.com/actions/setup-python + - name: Install uv and set Python version + uses: astral-sh/setup-uv@v7 with: + version: "0.11.2" python-version: ${{ matrix.python-version }} + enable-cache: true - - name: Check python version + - name: Check Python version run: python --version - - name: Install poetry - uses: abatilo/actions-poetry@v2 - - - name: Set poetry python version - run: | - poetry env use ${{ matrix.python-version }} - poetry env info - - - name: Setup a local virtual environment (if no poetry.toml file) - run: | - poetry config virtualenvs.create true --local - poetry config virtualenvs.in-project true --local - - - uses: actions/cache@v3 - name: Define a cache for the virtual environment based on the dependencies lock file - with: - path: ./.venv - key: | - venv-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}-${{ github.sha }} - - name: Install the project dependencies - run: poetry install --all-extras + run: uv sync --locked - name: Run the automated tests run: | python --version - poetry run pytest -n auto --dist loadfile -s -v --log-cli-level=INFO + uv run --frozen pytest -n auto --dist loadfile -s -v --log-cli-level=INFO all-tests-passed: # This allows us to have a branch protection rule for tests and deploys with matrix diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86ce3677b..af16d7927 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,17 +67,12 @@ jobs: fetch-depth: 0 token: ${{ secrets.GH_ACCESS_TOKEN }} - - name: Setup Python - uses: actions/setup-python@v5 + - name: Install uv and set Python version + uses: astral-sh/setup-uv@v7 with: + version: "0.11.2" python-version: "3.12" - - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: "1.8.4" - virtualenvs-create: true - virtualenvs-in-project: true + enable-cache: true - name: Configure Git env: @@ -92,7 +87,7 @@ jobs: - name: Get current version id: current-version run: | - current_version=$(poetry version -s) + current_version=$(uv version --short) echo "version=$current_version" >> $GITHUB_OUTPUT echo "Current version: $current_version" @@ -211,27 +206,13 @@ jobs: - name: Update version in pyproject.toml run: | - poetry version ${{ steps.new-version.outputs.version }} - - - name: Update version in langfuse/version.py - run: | - new_version="${{ steps.new-version.outputs.version }}" - sed -i "s/__version__ = \".*\"/__version__ = \"$new_version\"/" langfuse/version.py - echo "Updated langfuse/version.py:" - cat langfuse/version.py + uv version ${{ steps.new-version.outputs.version }} - name: Verify version consistency run: | - pyproject_version=$(poetry version -s) - file_version=$(grep -oP '__version__ = "\K[^"]+' langfuse/version.py) + pyproject_version=$(uv version --short) echo "pyproject.toml version: $pyproject_version" - echo "langfuse/version.py version: $file_version" - - if [ "$pyproject_version" != "$file_version" ]; then - echo "❌ Error: Version mismatch between pyproject.toml and langfuse/version.py" - exit 1 - fi if [ "$pyproject_version" != "${{ steps.new-version.outputs.version }}" ]; then echo "❌ Error: Version in files doesn't match expected version" @@ -241,7 +222,7 @@ jobs: echo "✅ Versions are consistent: $pyproject_version" - name: Build package - run: poetry build + run: uv build --no-sources - name: Verify build artifacts run: | @@ -278,9 +259,17 @@ jobs: fi echo "✅ Artifact version verified" + - name: Smoke test wheel + run: | + uv run --isolated --no-project --with dist/*.whl python -c "from importlib.metadata import version; import langfuse; assert langfuse.__version__ == version('langfuse')" + + - name: Smoke test source distribution + run: | + uv run --isolated --no-project --with dist/*.tar.gz python -c "from importlib.metadata import version; import langfuse; assert langfuse.__version__ == version('langfuse')" + - name: Commit version changes run: | - git add pyproject.toml langfuse/version.py + git add pyproject.toml uv.lock git commit -m "chore: release v${{ steps.new-version.outputs.version }}" - name: Create and push tag @@ -292,9 +281,7 @@ jobs: - name: Publish to PyPI id: publish-pypi - uses: pypa/gh-action-pypi-publish@release/v1 - with: - print-hash: true + run: uv publish --trusted-publishing always - name: Create GitHub Release id: create-release diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index efd690431..79eff24cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,35 +1,30 @@ repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.11.2 + hooks: + - id: uv-lock + + - repo: local hooks: # Run the linter and fix - - id: ruff-check + - id: uv-ruff-check + name: ruff-check + entry: uv run --frozen ruff check --fix + language: system types_or: [python, pyi, jupyter] - args: [--fix] + exclude: ^langfuse/api/ # Run the formatter. - - id: ruff-format + - id: uv-ruff-format + name: ruff-format + entry: uv run --frozen ruff format + language: system types_or: [python, pyi, jupyter] + exclude: ^langfuse/api/ - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.18.2 - hooks: - - id: mypy - additional_dependencies: - - types-requests - - types-setuptools - - httpx - - pydantic>=1.10.7 - - backoff>=1.10.0 - - openai>=0.27.8 - - wrapt - - packaging>=23.2 - - opentelemetry-api - - opentelemetry-sdk - - opentelemetry-exporter-otlp - - numpy - - langchain>=0.0.309 - - langchain-core - - langgraph - args: [--no-error-summary] + - id: uv-mypy + name: mypy + entry: uv run --frozen mypy langfuse --no-error-summary + language: system files: ^langfuse/ + pass_filenames: false diff --git a/CLAUDE.md b/CLAUDE.md index 51f3eaeeb..330193c59 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,60 +11,57 @@ This is the Langfuse Python SDK, a client library for accessing the Langfuse obs ### Setup ```bash -# Install Poetry plugins (one-time setup) -poetry self add poetry-dotenv-plugin - -# Install all dependencies including optional extras -poetry install --all-extras +# Install the project and development dependencies +uv sync # Setup pre-commit hooks -poetry run pre-commit install +uv run pre-commit install ``` ### Testing ```bash # Run all tests with verbose output -poetry run pytest -s -v --log-cli-level=INFO +uv run --env-file .env pytest -s -v --log-cli-level=INFO # Run a specific test -poetry run pytest -s -v --log-cli-level=INFO tests/test_core_sdk.py::test_flush +uv run --env-file .env pytest -s -v --log-cli-level=INFO tests/test_core_sdk.py::test_flush # Run tests in parallel (faster) -poetry run pytest -s -v --log-cli-level=INFO -n auto +uv run --env-file .env pytest -s -v --log-cli-level=INFO -n auto ``` ### Code Quality ```bash # Format code with Ruff -poetry run ruff format . +uv run ruff format . # Run linting (development config) -poetry run ruff check . +uv run ruff check . # Run type checking -poetry run mypy . +uv run mypy . # Run pre-commit hooks manually -poetry run pre-commit run --all-files +uv run pre-commit run --all-files ``` ### Building and Releasing ```bash # Build the package locally (for testing) -poetry build +uv build --no-sources # Generate documentation -poetry run pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse +uv run --group docs pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse ``` Releases are automated via GitHub Actions. To release: 1. Go to Actions > "Release Python SDK" workflow 2. Click "Run workflow" -3. Select version bump type (patch/minor/major/prerelease) +3. Select version bump type (patch/minor/major/prepatch/preminor/premajor) 4. For prereleases, select the type (alpha/beta/rc) The workflow handles versioning, building, PyPI publishing (via OIDC), and GitHub release creation. @@ -120,8 +117,8 @@ Environment variables (defined in `_client/environment_variables.py`): ## Important Files -- `pyproject.toml`: Poetry configuration, dependencies, and tool settings -- `langfuse/version.py`: Version string (updated by CI release workflow) +- `pyproject.toml`: uv project metadata, dependencies, and tool settings +- `uv.lock`: Locked dependency graph for local development and CI ## API Generation @@ -129,7 +126,7 @@ The `langfuse/api/` directory is auto-generated from the Langfuse OpenAPI specif 1. Generate new SDK in main Langfuse repo 2. Copy generated files from `generated/python` to `langfuse/api/` -3. Run `poetry run ruff format .` to format the generated code +3. Run `uv run ruff format .` to format the generated code ## Testing Guidelines diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14a42a10d..53fede752 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,29 +2,23 @@ ## Development -### Add Poetry plugins - -``` -poetry self add poetry-dotenv-plugin -``` - ### Install dependencies ``` -poetry install --all-extras +uv sync ``` ### Add Pre-commit ``` -poetry run pre-commit install +uv run pre-commit install ``` ### Type Checking To run type checking on the langfuse package, run: ```sh -poetry run mypy langfuse --no-error-summary +uv run mypy langfuse --no-error-summary ``` ### Tests @@ -38,13 +32,13 @@ poetry run mypy langfuse --no-error-summary - Run all ``` - poetry run pytest -s -v --log-cli-level=INFO + uv run --env-file .env pytest -s -v --log-cli-level=INFO ``` - Run a specific test ``` - poetry run pytest -s -v --log-cli-level=INFO tests/test_core_sdk.py::test_flush + uv run --env-file .env pytest -s -v --log-cli-level=INFO tests/test_core_sdk.py::test_flush ``` - E2E tests involving OpenAI and Serp API are usually skipped, remove skip decorators in [tests/test_langchain.py](tests/test_langchain.py) to run them. @@ -52,7 +46,7 @@ poetry run mypy langfuse --no-error-summary ### Update openapi spec 1. Generate Fern Python SDK in [langfuse](https://github.com/langfuse/langfuse) and copy the files generated in `generated/python` into the `langfuse/api` folder in this repo. -2. Execute the linter by running `poetry run ruff format .` +2. Execute the linter by running `uv run ruff format .` 3. Rebuild and deploy the package to PyPi. ### Publish release @@ -67,12 +61,12 @@ To create a release: - `patch` - Bug fixes (1.0.0 → 1.0.1) - `minor` - New features (1.0.0 → 1.1.0) - `major` - Breaking changes (1.0.0 → 2.0.0) - - `prerelease` - Pre-release versions (1.0.0 → 1.0.0a1) + - `prepatch`, `preminor`, or `premajor` - Pre-release versions (for example 1.0.0 → 1.0.1a1) 4. For pre-releases, select the type: `alpha`, `beta`, or `rc` 5. Click "Run workflow" The workflow will automatically: -- Bump the version in `pyproject.toml` and `langfuse/version.py` +- Bump the version in `pyproject.toml` - Build the package - Publish to PyPI - Create a git tag and GitHub release with auto-generated release notes @@ -81,22 +75,18 @@ The workflow will automatically: Note: The generated SDK reference is currently work in progress. -The SDK reference is generated via pdoc. You need to have all extra dependencies installed to generate the reference. - -```sh -poetry install --all-extras -``` +The SDK reference is generated via pdoc. The docs dependency group is installed on demand when you run the documentation commands. To update the reference, run the following command: ```sh -poetry run pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse +uv run --group docs pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse ``` To run the reference locally, you can use the following command: ```sh -poetry run pdoc --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse +uv run --group docs pdoc --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse ``` ## Credits diff --git a/langfuse/__init__.py b/langfuse/__init__.py index efb9773c0..d33febca7 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -28,6 +28,7 @@ LangfuseSpan, LangfuseTool, ) +from ._version import __version__ from .span_filter import ( KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES, is_default_export_span, @@ -62,6 +63,7 @@ "EvaluatorStats", "BatchEvaluationResumeToken", "BatchEvaluationResult", + "__version__", "is_default_export_span", "is_langfuse_span", "is_genai_span", diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 45c90ad66..55f23a782 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -46,7 +46,7 @@ from langfuse.logger import langfuse_logger from langfuse.types import MaskFunction -from ..version import __version__ as langfuse_version +from .._version import __version__ as langfuse_version class LangfuseResourceManager: diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index 3750789c0..1057137af 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -30,8 +30,8 @@ from langfuse._client.propagation import _get_propagated_attributes_from_context from langfuse._client.span_filter import is_default_export_span, is_langfuse_span from langfuse._client.utils import span_formatter +from langfuse._version import __version__ as langfuse_version from langfuse.logger import langfuse_logger -from langfuse.version import __version__ as langfuse_version class LangfuseSpanProcessor(BatchSpanProcessor): diff --git a/langfuse/_task_manager/score_ingestion_consumer.py b/langfuse/_task_manager/score_ingestion_consumer.py index 0aedaa7a5..dcb575263 100644 --- a/langfuse/_task_manager/score_ingestion_consumer.py +++ b/langfuse/_task_manager/score_ingestion_consumer.py @@ -13,7 +13,7 @@ from langfuse._utils.serializer import EventSerializer from langfuse.logger import langfuse_logger as logger -from ..version import __version__ as langfuse_version +from .._version import __version__ as langfuse_version MAX_EVENT_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_EVENT_SIZE_BYTES", 1_000_000)) MAX_BATCH_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_BATCH_SIZE_BYTES", 2_500_000)) diff --git a/langfuse/_version.py b/langfuse/_version.py new file mode 100644 index 000000000..38e2d8325 --- /dev/null +++ b/langfuse/_version.py @@ -0,0 +1,13 @@ +from functools import lru_cache +from importlib.metadata import PackageNotFoundError, version + + +@lru_cache(maxsize=1) +def get_langfuse_version() -> str: + try: + return version("langfuse") + except PackageNotFoundError: + return "0.0.0" + + +__version__ = get_langfuse_version() diff --git a/langfuse/version.py b/langfuse/version.py deleted file mode 100644 index a1bd9274f..000000000 --- a/langfuse/version.py +++ /dev/null @@ -1,3 +0,0 @@ -"""@private""" - -__version__ = "4.0.3" diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 4f95c4caa..000000000 --- a/poetry.lock +++ /dev/null @@ -1,2903 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.11.0" -description = "High-level concurrency and networking framework on top of asyncio or Trio" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, - {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -trio = ["trio (>=0.31.0)"] - -[[package]] -name = "attrs" -version = "25.4.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, - {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, -] - -[[package]] -name = "autoevals" -version = "0.0.130" -description = "Universal library for evaluating AI models" -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0"}, - {file = "autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d"}, -] - -[package.dependencies] -chevron = "*" -jsonschema = "*" -polyleven = "*" -pyyaml = "*" - -[package.extras] -all = ["IPython", "black (==22.6.0)", "braintrust", "build", "flake8", "flake8-isort", "isort (==5.12.0)", "numpy", "openai", "pre-commit", "pydoc-markdown", "pytest", "respx", "scipy", "twine"] -dev = ["IPython", "black (==22.6.0)", "braintrust", "build", "flake8", "flake8-isort", "isort (==5.12.0)", "openai", "pre-commit", "pytest", "respx", "twine"] -doc = ["pydoc-markdown"] -scipy = ["numpy", "scipy"] - -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." -optional = false -python-versions = "<3.11,>=3.8" -groups = ["dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, - {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, -] - -[[package]] -name = "certifi" -version = "2025.10.5" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.4" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, - {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, - {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, -] - -[[package]] -name = "chevron" -version = "0.14.0" -description = "Mustache templating language renderer" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"}, - {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} - -[[package]] -name = "distlib" -version = "0.4.0" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, -] - -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.1" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "filelock" -version = "3.20.3" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, - {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, -] - -[[package]] -name = "googleapis-common-protos" -version = "1.70.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, - {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, -] - -[package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0)"] - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.16" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "identify" -version = "2.6.15" -description = "File identification library for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, - {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.11" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "importlib-metadata" -version = "8.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, - {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jiter" -version = "0.11.1" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, - {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87b2821795e28cc990939b68ce7a038edea680a24910bd68a79d54ff3f03c02"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83f6fa494d8bba14ab100417c80e70d32d737e805cb85be2052d771c76fcd1f8"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fbc6aea1daa2ec6f5ed465f0c5e7b0607175062ceebbea5ca70dd5ddab58083"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:302288e2edc43174bb2db838e94688d724f9aad26c5fb9a74f7a5fb427452a6a"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85db563fe3b367bb568af5d29dea4d4066d923b8e01f3417d25ebecd958de815"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1c1ba2b6b22f775444ef53bc2d5778396d3520abc7b2e1da8eb0c27cb3ffb10"}, - {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:523be464b14f8fd0cc78da6964b87b5515a056427a2579f9085ce30197a1b54a"}, - {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25b99b3f04cd2a38fefb22e822e35eb203a2cd37d680dbbc0c0ba966918af336"}, - {file = "jiter-0.11.1-cp310-cp310-win32.whl", hash = "sha256:47a79e90545a596bb9104109777894033347b11180d4751a216afef14072dbe7"}, - {file = "jiter-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:cace75621ae9bd66878bf69fbd4dfc1a28ef8661e0c2d0eb72d3d6f1268eddf5"}, - {file = "jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2"}, - {file = "jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29"}, - {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9"}, - {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5"}, - {file = "jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a"}, - {file = "jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19"}, - {file = "jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869"}, - {file = "jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c"}, - {file = "jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54"}, - {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d"}, - {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250"}, - {file = "jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e"}, - {file = "jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87"}, - {file = "jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c"}, - {file = "jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663"}, - {file = "jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c"}, - {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646"}, - {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a"}, - {file = "jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b"}, - {file = "jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed"}, - {file = "jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d"}, - {file = "jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b"}, - {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58"}, - {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789"}, - {file = "jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec"}, - {file = "jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8"}, - {file = "jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676"}, - {file = "jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f"}, - {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a"}, - {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3"}, - {file = "jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea"}, - {file = "jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c"}, - {file = "jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991"}, - {file = "jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51"}, - {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437"}, - {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111"}, - {file = "jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7"}, - {file = "jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1"}, - {file = "jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe"}, - {file = "jiter-0.11.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:baa99c8db49467527658bb479857344daf0a14dff909b7f6714579ac439d1253"}, - {file = "jiter-0.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:860fe55fa3b01ad0edf2adde1098247ff5c303d0121f9ce028c03d4f88c69502"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:173dd349d99b6feaf5a25a6fbcaf3489a6f947708d808240587a23df711c67db"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14ac1dca837514cc946a6ac2c4995d9695303ecc754af70a3163d057d1a444ab"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69af47de5f93a231d5b85f7372d3284a5be8edb4cc758f006ec5a1406965ac5e"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:685f8b3abd3bbd3e06e4dfe2429ff87fd5d7a782701151af99b1fcbd80e31b2b"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d04afa2d4e5526e54ae8a58feea953b1844bf6e3526bc589f9de68e86d0ea01"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e92b927259035b50d8e11a8fdfe0ebd014d883e4552d37881643fa289a4bcf1"}, - {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e7bd8be4fad8d4c5558b7801770cd2da6c072919c6f247cc5336edb143f25304"}, - {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:121381a77a3c85987f3eba0d30ceaca9116f7463bedeec2fa79b2e7286b89b60"}, - {file = "jiter-0.11.1-cp39-cp39-win32.whl", hash = "sha256:160225407f6dfabdf9be1b44e22f06bc293a78a28ffa4347054698bd712dad06"}, - {file = "jiter-0.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:028e0d59bcdfa1079f8df886cdaefc6f515c27a5288dec956999260c7e4a7cfd"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960"}, - {file = "jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["dev"] -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, - {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" -referencing = ">=0.28.4" -rpds-py = ">=0.7.1" - -[package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, - {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, -] - -[package.dependencies] -referencing = ">=0.31.0" - -[[package]] -name = "langchain" -version = "1.0.1" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0.0,>=3.10.0" -groups = ["dev"] -files = [ - {file = "langchain-1.0.1-py3-none-any.whl", hash = "sha256:48fd616413fee4843f12cad49e8b74ad6fc159640142ca885d03bd925cd24503"}, - {file = "langchain-1.0.1.tar.gz", hash = "sha256:8bf60e096ca9c06626d9161a46651405ef1d12b5863f7679283666f83f760dc5"}, -] - -[package.dependencies] -langchain-core = ">=1.0.0,<2.0.0" -langgraph = ">=1.0.0,<1.1.0" -pydantic = ">=2.7.4,<3.0.0" - -[package.extras] -anthropic = ["langchain-anthropic"] -aws = ["langchain-aws"] -community = ["langchain-community"] -deepseek = ["langchain-deepseek"] -fireworks = ["langchain-fireworks"] -google-genai = ["langchain-google-genai"] -google-vertexai = ["langchain-google-vertexai"] -groq = ["langchain-groq"] -huggingface = ["langchain-huggingface"] -mistralai = ["langchain-mistralai"] -ollama = ["langchain-ollama"] -openai = ["langchain-openai"] -perplexity = ["langchain-perplexity"] -together = ["langchain-together"] -xai = ["langchain-xai"] - -[[package]] -name = "langchain-core" -version = "1.2.22" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0.0,>=3.10.0" -groups = ["dev"] -files = [ - {file = "langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b"}, - {file = "langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058"}, -] - -[package.dependencies] -jsonpatch = ">=1.33.0,<2.0.0" -langsmith = ">=0.3.45,<1.0.0" -packaging = ">=23.2.0" -pydantic = ">=2.7.4,<3.0.0" -pyyaml = ">=5.3.0,<7.0.0" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" -typing-extensions = ">=4.7.0,<5.0.0" -uuid-utils = ">=0.12.0,<1.0" - -[[package]] -name = "langchain-openai" -version = "0.3.34" -description = "An integration package connecting OpenAI and LangChain" -optional = false -python-versions = "<4.0.0,>=3.9.0" -groups = ["dev"] -files = [ - {file = "langchain_openai-0.3.34-py3-none-any.whl", hash = "sha256:08d61d68a6d869c70d542171e149b9065668dedfc4fafcd4de8aeb5b933030a9"}, - {file = "langchain_openai-0.3.34.tar.gz", hash = "sha256:57916d462be5b8fd19e5cb2f00d4e5cf0465266a292d583de2fc693a55ba190e"}, -] - -[package.dependencies] -langchain-core = ">=0.3.77,<2.0.0" -openai = ">=1.104.2,<3.0.0" -tiktoken = ">=0.7.0,<1.0.0" - -[[package]] -name = "langgraph" -version = "1.0.10" -description = "Building stateful, multi-actor applications with LLMs" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "langgraph-1.0.10-py3-none-any.whl", hash = "sha256:7c298bef4f6ea292fcf9824d6088fe41a6727e2904ad6066f240c4095af12247"}, - {file = "langgraph-1.0.10.tar.gz", hash = "sha256:73bd10ee14a8020f31ef07e9cd4c1a70c35cc07b9c2b9cd637509a10d9d51e29"}, -] - -[package.dependencies] -langchain-core = ">=0.1" -langgraph-checkpoint = ">=2.1.0,<5.0.0" -langgraph-prebuilt = ">=1.0.8,<1.1.0" -langgraph-sdk = ">=0.3.0,<0.4.0" -pydantic = ">=2.7.4" -xxhash = ">=3.5.0" - -[[package]] -name = "langgraph-checkpoint" -version = "4.0.0" -description = "Library with base interfaces for LangGraph checkpoint savers." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784"}, - {file = "langgraph_checkpoint-4.0.0.tar.gz", hash = "sha256:814d1bd050fac029476558d8e68d87bce9009a0262d04a2c14b918255954a624"}, -] - -[package.dependencies] -langchain-core = ">=0.2.38" -ormsgpack = ">=1.12.0" - -[[package]] -name = "langgraph-prebuilt" -version = "1.0.8" -description = "Library with high-level APIs for creating and executing LangGraph agents and tools." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0"}, - {file = "langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69"}, -] - -[package.dependencies] -langchain-core = ">=1.0.0" -langgraph-checkpoint = ">=2.1.0,<5.0.0" - -[[package]] -name = "langgraph-sdk" -version = "0.3.6" -description = "SDK for interacting with LangGraph API" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "langgraph_sdk-0.3.6-py3-none-any.whl", hash = "sha256:7df2fd552ad7262d0baf8e1f849dce1d62186e76dcdd36db9dc5bdfa5c3fc20f"}, - {file = "langgraph_sdk-0.3.6.tar.gz", hash = "sha256:7650f607f89c1586db5bee391b1a8754cbe1fc83b721ff2f1450f8906e790bd7"}, -] - -[package.dependencies] -httpx = ">=0.25.2" -orjson = ">=3.10.1" - -[[package]] -name = "langsmith" -version = "0.6.3" -description = "Client library to connect to the LangSmith Observability and Evaluation Platform." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "langsmith-0.6.3-py3-none-any.whl", hash = "sha256:44fdf8084165513e6bede9dda715e7b460b1b3f57ac69f2ca3f03afa911233ec"}, - {file = "langsmith-0.6.3.tar.gz", hash = "sha256:33246769c0bb24e2c17e0c34bb21931084437613cd37faf83bd0978a297b826f"}, -] - -[package.dependencies] -httpx = ">=0.23.0,<1" -orjson = {version = ">=3.9.14", markers = "platform_python_implementation != \"PyPy\""} -packaging = ">=23.2" -pydantic = ">=2,<3" -requests = ">=2.0.0" -requests-toolbelt = ">=1.0.0" -uuid-utils = ">=0.12.0,<1.0" -zstandard = ">=0.23.0" - -[package.extras] -claude-agent-sdk = ["claude-agent-sdk (>=0.1.0) ; python_version >= \"3.10\""] -langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] -openai-agents = ["openai-agents (>=0.0.3)"] -otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] -pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4)", "vcrpy (>=7.0.0)"] -vcr = ["vcrpy (>=7.0.0)"] - -[[package]] -name = "markupsafe" -version = "3.0.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["dev", "docs"] -files = [ - {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, - {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, - {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, - {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, - {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, - {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, - {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, - {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, - {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, -] - -[[package]] -name = "mypy" -version = "1.18.2" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, - {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, - {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, - {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, - {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, - {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, - {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, - {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, - {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, - {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, - {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, - {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, - {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, - {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, - {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, - {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, - {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, - {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, - {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, - {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, - {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, - {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, - {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, - {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, - {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, - {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, - {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, - {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, - {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, - {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, - {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, - {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, - {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, - {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, - {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, - {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, - {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, - {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "openai" -version = "2.5.0" -description = "The official Python library for the openai API" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "openai-2.5.0-py3-none-any.whl", hash = "sha256:21380e5f52a71666dbadbf322dd518bdf2b9d11ed0bb3f96bea17310302d6280"}, - {file = "openai-2.5.0.tar.gz", hash = "sha256:f8fa7611f96886a0f31ac6b97e58bc0ada494b255ee2cfd51c8eb502cfcb4814"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.10.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -tqdm = ">4" -typing-extensions = ">=4.11,<5" - -[package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] -datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -realtime = ["websockets (>=13,<16)"] -voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] - -[[package]] -name = "opentelemetry-api" -version = "1.38.0" -description = "OpenTelemetry Python API" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582"}, - {file = "opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12"}, -] - -[package.dependencies] -importlib-metadata = ">=6.0,<8.8.0" -typing-extensions = ">=4.5.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-common" -version = "1.38.0" -description = "OpenTelemetry Protobuf encoding" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c"}, -] - -[package.dependencies] -opentelemetry-proto = "1.38.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-http" -version = "1.38.0" -description = "OpenTelemetry Collector Protobuf over HTTP Exporter" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.52,<2.0" -opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.38.0" -opentelemetry-proto = "1.38.0" -opentelemetry-sdk = ">=1.38.0,<1.39.0" -requests = ">=2.7,<3.0" -typing-extensions = ">=4.5.0" - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.59b0" -description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee"}, - {file = "opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.59b0" -packaging = ">=18.0" -wrapt = ">=1.0.0,<2.0.0" - -[[package]] -name = "opentelemetry-instrumentation-threading" -version = "0.59b0" -description = "Thread context propagation support for OpenTelemetry" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca"}, - {file = "opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.59b0" -wrapt = ">=1.0.0,<2.0.0" - -[[package]] -name = "opentelemetry-proto" -version = "1.38.0" -description = "OpenTelemetry Python Proto" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18"}, - {file = "opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468"}, -] - -[package.dependencies] -protobuf = ">=5.0,<7.0" - -[[package]] -name = "opentelemetry-sdk" -version = "1.38.0" -description = "OpenTelemetry Python SDK" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b"}, - {file = "opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe"}, -] - -[package.dependencies] -opentelemetry-api = "1.38.0" -opentelemetry-semantic-conventions = "0.59b0" -typing-extensions = ">=4.5.0" - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.59b0" -description = "OpenTelemetry Semantic Conventions" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed"}, - {file = "opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0"}, -] - -[package.dependencies] -opentelemetry-api = "1.38.0" -typing-extensions = ">=4.5.0" - -[[package]] -name = "orjson" -version = "3.11.6" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "orjson-3.11.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a613fc37e007143d5b6286dccb1394cd114b07832417006a02b620ddd8279e37"}, - {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46ebee78f709d3ba7a65384cfe285bb0763157c6d2f836e7bde2f12d33a867a2"}, - {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a726fa86d2368cd57990f2bd95ef5495a6e613b08fc9585dfe121ec758fb08d1"}, - {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:150f12e59d6864197770c78126e1a6e07a3da73d1728731bf3bc1e8b96ffdbe6"}, - {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2d9746a5b5ce20c0908ada451eb56da4ffa01552a50789a0354d8636a02953"}, - {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd177f5dd91666d31e9019f1b06d2fcdf8a409a1637ddcb5915085dede85680"}, - {file = "orjson-3.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d777ec41a327bd3b7de97ba7bce12cc1007815ca398e4e4de9ec56c022c090b"}, - {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f3a135f83185c87c13ff231fcb7dbb2fa4332a376444bd65135b50ff4cc5265c"}, - {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:2a8eeed7d4544cf391a142b0dd06029dac588e96cc692d9ab1c3f05b1e57c7f6"}, - {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9d576865a21e5cc6695be8fb78afc812079fd361ce6a027a7d41561b61b33a90"}, - {file = "orjson-3.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:925e2df51f60aa50f8797830f2adfc05330425803f4105875bb511ced98b7f89"}, - {file = "orjson-3.11.6-cp310-cp310-win32.whl", hash = "sha256:09dded2de64e77ac0b312ad59f35023548fb87393a57447e1bb36a26c181a90f"}, - {file = "orjson-3.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:3a63b5e7841ca8635214c6be7c0bf0246aa8c5cd4ef0c419b14362d0b2fb13de"}, - {file = "orjson-3.11.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e259e85a81d76d9665f03d6129e09e4435531870de5961ddcd0bf6e3a7fde7d7"}, - {file = "orjson-3.11.6-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:52263949f41b4a4822c6b1353bcc5ee2f7109d53a3b493501d3369d6d0e7937a"}, - {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6439e742fa7834a24698d358a27346bb203bff356ae0402e7f5df8f749c621a8"}, - {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b81ffd68f084b4e993e3867acb554a049fa7787cc8710bbcc1e26965580d99be"}, - {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5a5468e5e60f7ef6d7f9044b06c8f94a3c56ba528c6e4f7f06ae95164b595ec"}, - {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72c5005eb45bd2535632d4f3bec7ad392832cfc46b62a3021da3b48a67734b45"}, - {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b14dd49f3462b014455a28a4d810d3549bf990567653eb43765cd847df09145"}, - {file = "orjson-3.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bb2c1ea30ef302f0f89f9bf3e7f9ab5e2af29dc9f80eb87aa99788e4e2d65"}, - {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:825e0a85d189533c6bff7e2fc417a28f6fcea53d27125c4551979aecd6c9a197"}, - {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b04575417a26530637f6ab4b1f7b4f666eb0433491091da4de38611f97f2fcf3"}, - {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b83eb2e40e8c4da6d6b340ee6b1d6125f5195eb1b0ebb7eac23c6d9d4f92d224"}, - {file = "orjson-3.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1f42da604ee65a6b87eef858c913ce3e5777872b19321d11e6fc6d21de89b64f"}, - {file = "orjson-3.11.6-cp311-cp311-win32.whl", hash = "sha256:5ae45df804f2d344cffb36c43fdf03c82fb6cd247f5faa41e21891b40dfbf733"}, - {file = "orjson-3.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:f4295948d65ace0a2d8f2c4ccc429668b7eb8af547578ec882e16bf79b0050b2"}, - {file = "orjson-3.11.6-cp311-cp311-win_arm64.whl", hash = "sha256:314e9c45e0b81b547e3a1cfa3df3e07a815821b3dac9fe8cb75014071d0c16a4"}, - {file = "orjson-3.11.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6f03f30cd8953f75f2a439070c743c7336d10ee940da918d71c6f3556af3ddcf"}, - {file = "orjson-3.11.6-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:af44baae65ef386ad971469a8557a0673bb042b0b9fd4397becd9c2dfaa02588"}, - {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c310a48542094e4f7dbb6ac076880994986dda8ca9186a58c3cb70a3514d3231"}, - {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8dfa7a5d387f15ecad94cb6b2d2d5f4aeea64efd8d526bfc03c9812d01e1cc0"}, - {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba8daee3e999411b50f8b50dbb0a3071dd1845f3f9a1a0a6fa6de86d1689d84d"}, - {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89d104c974eafd7436d7a5fdbc57f7a1e776789959a2f4f1b2eab5c62a339f4"}, - {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e2e2456788ca5ea75616c40da06fc885a7dc0389780e8a41bf7c5389ba257b"}, - {file = "orjson-3.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a42efebc45afabb1448001e90458c4020d5c64fbac8a8dc4045b777db76cb5a"}, - {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71b7cbef8471324966c3738c90ba38775563ef01b512feb5ad4805682188d1b9"}, - {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f8515e5910f454fe9a8e13c2bb9dc4bae4c1836313e967e72eb8a4ad874f0248"}, - {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:300360edf27c8c9bf7047345a94fddf3a8b8922df0ff69d71d854a170cb375cf"}, - {file = "orjson-3.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:caaed4dad39e271adfadc106fab634d173b2bb23d9cf7e67bd645f879175ebfc"}, - {file = "orjson-3.11.6-cp312-cp312-win32.whl", hash = "sha256:955368c11808c89793e847830e1b1007503a5923ddadc108547d3b77df761044"}, - {file = "orjson-3.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:2c68de30131481150073d90a5d227a4a421982f42c025ecdfb66157f9579e06f"}, - {file = "orjson-3.11.6-cp312-cp312-win_arm64.whl", hash = "sha256:65dfa096f4e3a5e02834b681f539a87fbe85adc82001383c0db907557f666bfc"}, - {file = "orjson-3.11.6-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e4ae1670caabb598a88d385798692ce2a1b2f078971b3329cfb85253c6097f5b"}, - {file = "orjson-3.11.6-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:2c6b81f47b13dac2caa5d20fbc953c75eb802543abf48403a4703ed3bff225f0"}, - {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:647d6d034e463764e86670644bdcaf8e68b076e6e74783383b01085ae9ab334f"}, - {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8523b9cc4ef174ae52414f7699e95ee657c16aa18b3c3c285d48d7966cce9081"}, - {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:313dfd7184cde50c733fc0d5c8c0e2f09017b573afd11dc36bd7476b30b4cb17"}, - {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905ee036064ff1e1fd1fb800055ac477cdcb547a78c22c1bc2bbf8d5d1a6fb42"}, - {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce374cb98411356ba906914441fc993f271a7a666d838d8de0e0900dd4a4bc12"}, - {file = "orjson-3.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cded072b9f65fcfd188aead45efa5bd528ba552add619b3ad2a81f67400ec450"}, - {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ab85bdbc138e1f73a234db6bb2e4cc1f0fcec8f4bd2bd2430e957a01aadf746"}, - {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:351b96b614e3c37a27b8ab048239ebc1e0be76cc17481a430d70a77fb95d3844"}, - {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f9959c85576beae5cdcaaf39510b15105f1ee8b70d5dacd90152617f57be8c83"}, - {file = "orjson-3.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75682d62b1b16b61a30716d7a2ec1f4c36195de4a1c61f6665aedd947b93a5d5"}, - {file = "orjson-3.11.6-cp313-cp313-win32.whl", hash = "sha256:40dc277999c2ef227dcc13072be879b4cfd325502daeb5c35ed768f706f2bf30"}, - {file = "orjson-3.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:f0f6e9f8ff7905660bc3c8a54cd4a675aa98f7f175cf00a59815e2ff42c0d916"}, - {file = "orjson-3.11.6-cp313-cp313-win_arm64.whl", hash = "sha256:1608999478664de848e5900ce41f25c4ecdfc4beacbc632b6fd55e1a586e5d38"}, - {file = "orjson-3.11.6-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6026db2692041d2a23fe2545606df591687787825ad5821971ef0974f2c47630"}, - {file = "orjson-3.11.6-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:132b0ab2e20c73afa85cf142e547511feb3d2f5b7943468984658f3952b467d4"}, - {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b376fb05f20a96ec117d47987dd3b39265c635725bda40661b4c5b73b77b5fde"}, - {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:954dae4e080574672a1dfcf2a840eddef0f27bd89b0e94903dd0824e9c1db060"}, - {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe515bb89d59e1e4b48637a964f480b35c0a2676de24e65e55310f6016cca7ce"}, - {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:380f9709c275917af28feb086813923251e11ee10687257cd7f1ea188bcd4485"}, - {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8173e0d3f6081e7034c51cf984036d02f6bab2a2126de5a759d79f8e5a140e7"}, - {file = "orjson-3.11.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dddf9ba706294906c56ef5150a958317b09aa3a8a48df1c52ccf22ec1907eac"}, - {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cbae5c34588dc79938dffb0b6fbe8c531f4dc8a6ad7f39759a9eb5d2da405ef2"}, - {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f75c318640acbddc419733b57f8a07515e587a939d8f54363654041fd1f4e465"}, - {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e0ab8d13aa2a3e98b4a43487c9205b2c92c38c054b4237777484d503357c8437"}, - {file = "orjson-3.11.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f884c7fb1020d44612bd7ac0db0babba0e2f78b68d9a650c7959bf99c783773f"}, - {file = "orjson-3.11.6-cp314-cp314-win32.whl", hash = "sha256:8d1035d1b25732ec9f971e833a3e299d2b1a330236f75e6fd945ad982c76aaf3"}, - {file = "orjson-3.11.6-cp314-cp314-win_amd64.whl", hash = "sha256:931607a8865d21682bb72de54231655c86df1870502d2962dbfd12c82890d077"}, - {file = "orjson-3.11.6-cp314-cp314-win_arm64.whl", hash = "sha256:fe71f6b283f4f1832204ab8235ce07adad145052614f77c876fcf0dac97bc06f"}, - {file = "orjson-3.11.6.tar.gz", hash = "sha256:0a54c72259f35299fd033042367df781c2f66d10252955ca1efb7db309b954cb"}, -] - -[[package]] -name = "ormsgpack" -version = "1.12.2" -description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657"}, - {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163"}, - {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59f5da97000c12bc2d50e988bdc8576b21f6ab4e608489879d35b2c07a8ab51a"}, - {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e4459c3f27066beadb2b81ea48a076a417aafffff7df1d3c11c519190ed44f2"}, - {file = "ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a1c460655d7288407ffa09065e322a7231997c0d62ce914bf3a96ad2dc6dedd"}, - {file = "ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:458e4568be13d311ef7d8877275e7ccbe06c0e01b39baaac874caaa0f46d826c"}, - {file = "ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cde5eaa6c6cbc8622db71e4a23de56828e3d876aeb6460ffbcb5b8aff91093b"}, - {file = "ormsgpack-1.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc7a33be14c347893edbb1ceda89afbf14c467d593a5ee92c11de4f1666b4d4f"}, - {file = "ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9"}, - {file = "ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a"}, - {file = "ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5"}, - {file = "ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181"}, - {file = "ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b"}, - {file = "ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92"}, - {file = "ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a"}, - {file = "ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c"}, - {file = "ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd"}, - {file = "ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7"}, - {file = "ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d"}, - {file = "ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e"}, - {file = "ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc"}, - {file = "ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e"}, - {file = "ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6"}, - {file = "ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd"}, - {file = "ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4"}, - {file = "ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6"}, - {file = "ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355"}, - {file = "ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1"}, - {file = "ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172"}, - {file = "ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d"}, - {file = "ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7"}, - {file = "ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685"}, - {file = "ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258"}, - {file = "ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9"}, - {file = "ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709"}, - {file = "ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c"}, - {file = "ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553"}, - {file = "ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13"}, - {file = "ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d"}, - {file = "ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede"}, - {file = "ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e"}, - {file = "ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285"}, - {file = "ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f"}, - {file = "ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c"}, - {file = "ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8"}, - {file = "ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033"}, - {file = "ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d"}, - {file = "ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2"}, - {file = "ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pdoc" -version = "15.0.4" -description = "API Documentation for Python Projects" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4"}, - {file = "pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82"}, -] - -[package.dependencies] -Jinja2 = ">=2.11.0" -MarkupSafe = ">=1.1.1" -pygments = ">=2.12.0" - -[[package]] -name = "platformdirs" -version = "4.5.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, - {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, -] - -[package.extras] -docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] -type = ["mypy (>=1.18.2)"] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "polyleven" -version = "0.9.0" -description = "A fast C-implemented library for Levenshtein distance" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "polyleven-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e00207fbe0fcdde206b9b277cf14bb9db8801f8d303204b1572870797399974"}, - {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d400f255af038f77b37d5010532e0e82d07160457c8282e5b40632987ab815be"}, - {file = "polyleven-0.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a1d3f1b385e9f51090beca54925a0fd0ab2d744fcea91dd9353c7b13bbb274f"}, - {file = "polyleven-0.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2be92bb7743e3b3e14a2b894902f4ceeea5700849dd9e9ab59c68bd7943b3d85"}, - {file = "polyleven-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bd784bad5164d0d4e823d98aa8ffdc118c14d211dfd7271ede7f1baa7efc691"}, - {file = "polyleven-0.9.0-cp310-cp310-win32.whl", hash = "sha256:bac610f5a30b56ab2fbb1a3de071ef9ed3aa6a572a80a4cfbf0665929e0f6451"}, - {file = "polyleven-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4e4ab3cfc196907751adb3b65959ad8be08fc06679d071fdf01e5225f394812e"}, - {file = "polyleven-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e58bbcd3f062043fa67e76e89f803eb308ea06fbb4dc6f32d7063c37f1c16dfd"}, - {file = "polyleven-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fd803de02e99f51ade3fcae4e5be50c89c1ff360213bcdbcf98820e2633c71a"}, - {file = "polyleven-0.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff60e2da0864b3d4bec2826eadbbb0a8967384d53bec9e693aad7b0089e1258c"}, - {file = "polyleven-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:259856641423ca82230237d637869301ba02971c24283101b67c8117e7116b7a"}, - {file = "polyleven-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a46e7b364b3936f025022d1182e10cba9ac45974dc2cafa17b7f9f515784adb5"}, - {file = "polyleven-0.9.0-cp311-cp311-win32.whl", hash = "sha256:6f0fd999efaa0d5409603ae7e44b60152b8d12a190b54115bcf0ba93e41e09f1"}, - {file = "polyleven-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:65a6e899db184bce6384526e46f446c6c159a2b0bb3b463dcc78a2bc8ddf85f5"}, - {file = "polyleven-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9c905fa0862c1f3e27e948a713fb86a26ce1659f1d90b1b4aff04a8890213b"}, - {file = "polyleven-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7058bea0da4893ebb8bedd9f638ec4e026c150e29b7b7385db5c157742d0ff11"}, - {file = "polyleven-0.9.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99fcfc48c1eaacc4a46dd9d22dc98de111120c66b56df14257f276b762bd591"}, - {file = "polyleven-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:29ef7db85a7bb01be9372461bc8d8993d4817dfcea702e4d2b8f0d9c43415ebe"}, - {file = "polyleven-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:288bfe0a0040421c52a5dc312b55c47812a72fb9cd7e6d19859ac2f9f11f350f"}, - {file = "polyleven-0.9.0-cp312-cp312-win32.whl", hash = "sha256:7260fa32fff7194e06b4221e0a6d2ba2decd4e4dc51f7f8cddbf365649326ee4"}, - {file = "polyleven-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4db8b16aac237dbf644a0e4323c3ba0907dab6adecd2a345bf2fa92301d7fb2d"}, - {file = "polyleven-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45cea2885c61bda9711244a51aed068f9a55f1d776d4caad6c574a3f401945ae"}, - {file = "polyleven-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62b039e9dc8fa53ad740de02d168a7e9d0edce3734b2927f40fe851b328b766f"}, - {file = "polyleven-0.9.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a0c1ecd2dc356fd94edc80e18a30ad28e93ccc840127e765b83ad60426b2d5"}, - {file = "polyleven-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:20576da0c8000bd1c4a07cee43db9169b7d094f5dcc03b20775506d07c56f4fb"}, - {file = "polyleven-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba356ce9e7e7e8ddf4eff17eb39df5b822cb8899450c6d289a22249b78c9a5f4"}, - {file = "polyleven-0.9.0-cp313-cp313-win32.whl", hash = "sha256:244d759986486252121061d727a642d3505cbdd9e6616467b42935e662a9fa61"}, - {file = "polyleven-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f671df664924b3ec14195be7bf778d5f71811989e59a3f9547f8066cefc596f"}, - {file = "polyleven-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7309296f1f91e7aa7d292e5b9aa0da53f2ce7997cfda8535155424a791fe73c8"}, - {file = "polyleven-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c71e238153acdf010c7fe6f18835dd6d7ca37a7e7cca08d51c2234e2227019"}, - {file = "polyleven-0.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecf0a858b7694acea0f7459f8699f8b1f62ee99d88529b01f3a1597aa4c53978"}, - {file = "polyleven-0.9.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c903c9b70a089c5f2b5990ce3a09ac1ce39d0b1ea93ec8c9e1eb217ddea779c6"}, - {file = "polyleven-0.9.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e9608f5835f8fb3778aaad2b126aaea201cd9a6b210286533762c29cd3debcf2"}, - {file = "polyleven-0.9.0-cp38-cp38-win32.whl", hash = "sha256:aabd963fef557f6afe4306920cbd6c580aff572c8a96c5d6bf572fb9c4bdce46"}, - {file = "polyleven-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:e8c4c3c6515f4753fe69becb4686009bc5a5776752fd27a3d34d89f54f8c40e6"}, - {file = "polyleven-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c672c982108a48c7aebd7016aa8482b8ee96f01280a68cbee56293055aebdfc7"}, - {file = "polyleven-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a4f857c9f7fd99b7e41305e6cdb30d39592b1a6ca50fbc20edd175746e376ca"}, - {file = "polyleven-0.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26e06e1da0734c8d5a1625589d2bd213f9d40d0023370475c167dc773239ab78"}, - {file = "polyleven-0.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9859199fefc85329b495cd0ce5b34df1a9acf6623d3dbaff5fcb688ade59fb88"}, - {file = "polyleven-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58703ae7483b46a5e05d2d3f2cac2e345b96b57faaebfe09c5890eb5346daf31"}, - {file = "polyleven-0.9.0-cp39-cp39-win32.whl", hash = "sha256:92a0d2e4d6230f2ccc14d12d11cb496d5d5b81d975841bfed9dce6d11cf90826"}, - {file = "polyleven-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:1d651a6714caf4d144f8cb0bd6b1eb043a2ca80dd7c6d87b8f8020edc1729149"}, - {file = "polyleven-0.9.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0a59f3cf5297e22aac73cf439e1e9cb0703af1adc853fb911637172db09bddec"}, - {file = "polyleven-0.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3c8581d8eae56d0e0e3cce33384b4365ef29a924f48edc6b3b5a694412c4b7d"}, - {file = "polyleven-0.9.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:603f0ea18dc0826f7078c14484c227dcdb61ca8e4485d0b67f2df317a3a01726"}, - {file = "polyleven-0.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8cf8ff07ea44947e9a34ab371a3b0fec4d2328957332185445cfdd1675539cb9"}, - {file = "polyleven-0.9.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf4fb8f5be74b9bf7e6f7c2014ee153dc4208af337b781cf3aafc5f51a647d80"}, - {file = "polyleven-0.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f21e6c050f6f0d259cf9c6367042ba6a69e553b8294143c83bb47f6481486f9c"}, - {file = "polyleven-0.9.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c74d8cba499541fe96e96a76cb8ac2bac7f3d7efeb8c2cec1bf1383c91790f4"}, - {file = "polyleven-0.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5260411e820a858728d32f161690a54bc2162644dba8f4e2b0dd72707d00ac20"}, - {file = "polyleven-0.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:81ae9a154c82d53ff67d6cd6b4ee96de3e449f2c8cccd49aaa62b50f6e57a4eb"}, - {file = "polyleven-0.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef398fe2759f84a6c088320742f09ecef5904e5c1f60668eed08f431221c5239"}, - {file = "polyleven-0.9.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3163f6c7ad192ee14ef760b1dd3143a3107c483a327dcfb5e6c94d4c8217fa4"}, - {file = "polyleven-0.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:87ef064bfe4a1b13414e440f56a716096375ec93cf1351bed9a84942c230c715"}, - {file = "polyleven-0.9.0.tar.gz", hash = "sha256:299a93766761b5e5fb4092388f3dc6401224fd436c05f11c4ee48b262587e8da"}, -] - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "protobuf" -version = "6.33.5" -description = "" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"}, - {file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"}, - {file = "protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5"}, - {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190"}, - {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd"}, - {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0"}, - {file = "protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c"}, - {file = "protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a"}, - {file = "protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02"}, - {file = "protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c"}, -] - -[[package]] -name = "pydantic" -version = "2.12.3" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, - {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.41.4" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] - -[[package]] -name = "pydantic-core" -version = "2.41.4" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, - {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, -] - -[package.dependencies] -typing-extensions = ">=4.14.1" - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["dev", "docs"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "1.1.1" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest_asyncio-1.1.1-py3-none-any.whl", hash = "sha256:726339d30fcfde24691f589445b9b67d058b311ac632b1d704e97f20f1d878da"}, - {file = "pytest_asyncio-1.1.1.tar.gz", hash = "sha256:b72d215c38e2c91dbb32f275e0b5be69602d7869910e109360e375129960a649"}, -] - -[package.dependencies] -backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} -pytest = ">=8.2,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-httpserver" -version = "1.1.3" -description = "pytest-httpserver is a httpserver for pytest" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9"}, - {file = "pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec"}, -] - -[package.dependencies] -Werkzeug = ">=2.0.0" - -[[package]] -name = "pytest-timeout" -version = "2.4.0" -description = "pytest plugin to abort hanging tests" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2"}, - {file = "pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, - {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "pyyaml" -version = "6.0.3" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, - {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, - {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, - {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, - {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, - {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, - {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, - {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, - {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, - {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, - {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, - {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, - {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, -] - -[[package]] -name = "referencing" -version = "0.37.0" -description = "JSON Referencing + Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, - {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -rpds-py = ">=0.7.0" -typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} - -[[package]] -name = "regex" -version = "2025.9.18" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, - {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, - {file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"}, - {file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"}, - {file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"}, - {file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"}, - {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"}, - {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"}, - {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"}, - {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"}, - {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"}, - {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"}, - {file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"}, - {file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"}, - {file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"}, - {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"}, - {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"}, - {file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"}, - {file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"}, - {file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"}, - {file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"}, - {file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"}, - {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"}, - {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"}, - {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"}, - {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"}, - {file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"}, - {file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"}, - {file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"}, - {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"}, - {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"}, - {file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"}, - {file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"}, - {file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"}, - {file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"}, - {file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"}, - {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"}, - {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"}, - {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"}, - {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"}, - {file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"}, - {file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"}, - {file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"}, - {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"}, - {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"}, - {file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"}, - {file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"}, - {file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"}, - {file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"}, - {file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"}, - {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"}, - {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"}, - {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"}, - {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"}, - {file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"}, - {file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"}, - {file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"}, - {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"}, - {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"}, - {file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"}, - {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"}, - {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"}, - {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"}, - {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"}, - {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"}, - {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"}, - {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"}, - {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"}, - {file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"}, - {file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"}, - {file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"}, - {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"}, - {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"}, - {file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"}, - {file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"}, - {file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"}, - {file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"}, - {file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"}, - {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"}, - {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"}, - {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"}, - {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"}, - {file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"}, - {file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"}, - {file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"}, - {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"}, - {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"}, - {file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"}, - {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"}, - {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"}, - {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"}, - {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"}, - {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"}, - {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"}, - {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"}, - {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"}, - {file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"}, - {file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"}, - {file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"}, - {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"}, - {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"}, - {file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"}, - {file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"}, - {file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"}, - {file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"}, - {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"}, - {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"}, - {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"}, - {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"}, - {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"}, - {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"}, - {file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"}, - {file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"}, - {file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"}, - {file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"}, -] - -[[package]] -name = "requests" -version = "2.33.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b"}, - {file = "requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652"}, -] - -[package.dependencies] -certifi = ">=2023.5.7" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.26,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -test = ["PySocks (>=1.5.6,!=1.5.7)", "pytest (>=3)", "pytest-cov", "pytest-httpbin (==2.1.0)", "pytest-mock", "pytest-xdist"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] - -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -description = "A utility belt for advanced users of python-requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] -files = [ - {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, - {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, -] - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "rpds-py" -version = "0.27.1" -description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, - {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9"}, - {file = "rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4"}, - {file = "rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1"}, - {file = "rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881"}, - {file = "rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948"}, - {file = "rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39"}, - {file = "rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15"}, - {file = "rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746"}, - {file = "rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90"}, - {file = "rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998"}, - {file = "rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39"}, - {file = "rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594"}, - {file = "rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502"}, - {file = "rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b"}, - {file = "rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002"}, - {file = "rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3"}, - {file = "rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83"}, - {file = "rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d"}, - {file = "rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228"}, - {file = "rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7"}, - {file = "rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688"}, - {file = "rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797"}, - {file = "rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334"}, - {file = "rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675"}, - {file = "rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3"}, - {file = "rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456"}, - {file = "rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3"}, - {file = "rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2"}, - {file = "rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0"}, - {file = "rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a"}, - {file = "rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772"}, - {file = "rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527"}, - {file = "rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b"}, - {file = "rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52"}, - {file = "rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859"}, - {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"}, -] - -[[package]] -name = "ruff" -version = "0.15.2" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d"}, - {file = "ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e"}, - {file = "ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a"}, - {file = "ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956"}, - {file = "ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4"}, - {file = "ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de"}, - {file = "ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c"}, - {file = "ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8"}, - {file = "ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f"}, - {file = "ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5"}, - {file = "ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e"}, - {file = "ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "tenacity" -version = "9.1.2" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, - {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "tiktoken" -version = "0.12.0" -description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, - {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}, - {file = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}, - {file = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}, - {file = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}, - {file = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}, - {file = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}, - {file = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec"}, - {file = "tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3"}, - {file = "tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}, -] - -[package.dependencies] -regex = ">=2022.1.18" -requests = ">=2.26.0" - -[package.extras] -blobfile = ["blobfile (>=2)"] - -[[package]] -name = "tomli" -version = "2.3.0" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, - {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, - {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, - {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, - {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, - {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, - {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, - {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, - {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, - {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, - {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, - {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, - {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, - {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, -] - -[[package]] -name = "tqdm" -version = "4.67.1" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, - {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[[package]] -name = "urllib3" -version = "2.6.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, -] - -[package.extras] -brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] - -[[package]] -name = "uuid-utils" -version = "0.12.0" -description = "Drop-in replacement for Python UUID with bindings in Rust" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514"}, - {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65"}, - {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8fce83ecb3b16af29c7809669056c4b6e7cc912cab8c6d07361645de12dd79"}, - {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec921769afcb905035d785582b0791d02304a7850fbd6ce924c1a8976380dfc6"}, - {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f3b060330f5899a92d5c723547dc6a95adef42433e9748f14c66859a7396664"}, - {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:908dfef7f0bfcf98d406e5dc570c25d2f2473e49b376de41792b6e96c1d5d291"}, - {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c6a24148926bd0ca63e8a2dabf4cc9dc329a62325b3ad6578ecd60fbf926506"}, - {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:64a91e632669f059ef605f1771d28490b1d310c26198e46f754e8846dddf12f4"}, - {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:93c082212470bb4603ca3975916c205a9d7ef1443c0acde8fbd1e0f5b36673c7"}, - {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:431b1fb7283ba974811b22abd365f2726f8f821ab33f0f715be389640e18d039"}, - {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd7838c40149100299fa37cbd8bab5ee382372e8e65a148002a37d380df7c8"}, - {file = "uuid_utils-0.12.0-cp39-abi3-win32.whl", hash = "sha256:487f17c0fee6cbc1d8b90fe811874174a9b1b5683bf2251549e302906a50fed3"}, - {file = "uuid_utils-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:9598e7c9da40357ae8fffc5d6938b1a7017f09a1acbcc95e14af8c65d48c655a"}, - {file = "uuid_utils-0.12.0-cp39-abi3-win_arm64.whl", hash = "sha256:c9bea7c5b2aa6f57937ebebeee4d4ef2baad10f86f1b97b58a3f6f34c14b4e84"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e2209d361f2996966ab7114f49919eb6aaeabc6041672abbbbf4fdbb8ec1acc0"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d9636bcdbd6cfcad2b549c352b669412d0d1eb09be72044a2f13e498974863cd"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cd8543a3419251fb78e703ce3b15fdfafe1b7c542cf40caf0775e01db7e7674"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e98db2d8977c052cb307ae1cb5cc37a21715e8d415dbc65863b039397495a013"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8f2bdf5e4ffeb259ef6d15edae92aed60a1d6f07cbfab465d836f6b12b48da8"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c3ec53c0cb15e1835870c139317cc5ec06e35aa22843e3ed7d9c74f23f23898"}, - {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84e5c0eba209356f7f389946a3a47b2cc2effd711b3fc7c7f155ad9f7d45e8a3"}, - {file = "uuid_utils-0.12.0.tar.gz", hash = "sha256:252bd3d311b5d6b7f5dfce7a5857e27bb4458f222586bb439463231e5a9cbd64"}, -] - -[[package]] -name = "virtualenv" -version = "20.36.1" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, - {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} -platformdirs = ">=3.9.1,<5" -typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - -[[package]] -name = "werkzeug" -version = "3.1.6" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, - {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, -] - -[package.dependencies] -markupsafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "wrapt" -version = "1.17.3" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, - {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, - {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, - {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, - {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, - {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, - {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, - {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, - {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, - {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, - {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, - {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, - {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, - {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, - {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, - {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, - {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, - {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, - {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, - {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, - {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, - {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, - {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, - {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, - {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, - {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, - {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, - {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, - {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, - {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, - {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, - {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, - {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, - {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, - {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, - {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, - {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, - {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, - {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, - {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, - {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, - {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, - {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, - {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, - {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, - {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, - {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, - {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, - {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, - {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, - {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, - {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, - {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, - {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, - {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, - {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, - {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, - {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, - {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, - {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, -] - -[[package]] -name = "xxhash" -version = "3.6.0" -description = "Python binding for xxHash" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, - {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b"}, - {file = "xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b"}, - {file = "xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb"}, - {file = "xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d"}, - {file = "xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a"}, - {file = "xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3"}, - {file = "xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd"}, - {file = "xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef"}, - {file = "xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7"}, - {file = "xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c"}, - {file = "xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae"}, - {file = "xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb"}, - {file = "xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c"}, - {file = "xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829"}, - {file = "xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec"}, - {file = "xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd"}, - {file = "xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799"}, - {file = "xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392"}, - {file = "xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6"}, - {file = "xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702"}, - {file = "xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033"}, - {file = "xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec"}, - {file = "xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8"}, - {file = "xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746"}, - {file = "xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e"}, - {file = "xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5"}, - {file = "xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f"}, - {file = "xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad"}, - {file = "xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679"}, - {file = "xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4"}, - {file = "xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518"}, - {file = "xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119"}, - {file = "xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f"}, - {file = "xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95"}, - {file = "xxhash-3.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dac94fad14a3d1c92affb661021e1d5cbcf3876be5f5b4d90730775ccb7ac41"}, - {file = "xxhash-3.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6965e0e90f1f0e6cb78da568c13d4a348eeb7f40acfd6d43690a666a459458b8"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab89a6b80f22214b43d98693c30da66af910c04f9858dd39c8e570749593d7e"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4903530e866b7a9c1eadfd3fa2fbe1b97d3aed4739a80abf506eb9318561c850"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4da8168ae52c01ac64c511d6f4a709479da8b7a4a1d7621ed51652f93747dffa"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97460eec202017f719e839a0d3551fbc0b2fcc9c6c6ffaa5af85bbd5de432788"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aae0c9df92e7fa46fbb738737324a563c727990755ec1965a6a339ea10a1df"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d50101e57aad86f4344ca9b32d091a2135a9d0a4396f19133426c88025b09f1"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9085e798c163ce310d91f8aa6b325dda3c2944c93c6ce1edb314030d4167cc65"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a87f271a33fad0e5bf3be282be55d78df3a45ae457950deb5241998790326f87"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:9e040d3e762f84500961791fa3709ffa4784d4dcd7690afc655c095e02fff05f"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b0359391c3dad6de872fefb0cf5b69d55b0655c55ee78b1bb7a568979b2ce96b"}, - {file = "xxhash-3.6.0-cp38-cp38-win32.whl", hash = "sha256:e4ff728a2894e7f436b9e94c667b0f426b9c74b71f900cf37d5468c6b5da0536"}, - {file = "xxhash-3.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:01be0c5b500c5362871fc9cfdf58c69b3e5c4f531a82229ddb9eb1eb14138004"}, - {file = "xxhash-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc604dc06027dbeb8281aeac5899c35fcfe7c77b25212833709f0bff4ce74d2a"}, - {file = "xxhash-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:277175a73900ad43a8caeb8b99b9604f21fe8d7c842f2f9061a364a7e220ddb7"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfbc5b91397c8c2972fdac13fb3e4ed2f7f8ccac85cd2c644887557780a9b6e2"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2762bfff264c4e73c0e507274b40634ff465e025f0eaf050897e88ec8367575d"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f171a900d59d51511209f7476933c34a0c2c711078d3c80e74e0fe4f38680ec"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:780b90c313348f030b811efc37b0fa1431163cb8db8064cf88a7936b6ce5f222"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b242455eccdfcd1fa4134c431a30737d2b4f045770f8fe84356b3469d4b919"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a75ffc1bd5def584129774c158e108e5d768e10b75813f2b32650bb041066ed6"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1fc1ed882d1e8df932a66e2999429ba6cc4d5172914c904ab193381fba825360"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:44e342e8cc11b4e79dae5c57f2fb6360c3c20cc57d32049af8f567f5b4bcb5f4"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c2f9ccd5c4be370939a2e17602fbc49995299203da72a3429db013d44d590e86"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02ea4cb627c76f48cd9fb37cf7ab22bd51e57e1b519807234b473faebe526796"}, - {file = "xxhash-3.6.0-cp39-cp39-win32.whl", hash = "sha256:6551880383f0e6971dc23e512c9ccc986147ce7bfa1cd2e4b520b876c53e9f3d"}, - {file = "xxhash-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:7c35c4cdc65f2a29f34425c446f2f5cdcd0e3c34158931e1cc927ece925ab802"}, - {file = "xxhash-3.6.0-cp39-cp39-win_arm64.whl", hash = "sha256:ffc578717a347baf25be8397cb10d2528802d24f94cfc005c0e44fef44b5cdd6"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d"}, - {file = "xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6"}, -] - -[[package]] -name = "zipp" -version = "3.23.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - -[[package]] -name = "zstandard" -version = "0.25.0" -description = "Zstandard bindings for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, - {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, - {file = "zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550"}, - {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d"}, - {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b"}, - {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0"}, - {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e"}, - {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74"}, - {file = "zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa"}, - {file = "zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e"}, - {file = "zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c"}, - {file = "zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f"}, - {file = "zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431"}, - {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a"}, - {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc"}, - {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6"}, - {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa"}, - {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7"}, - {file = "zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4"}, - {file = "zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2"}, - {file = "zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137"}, - {file = "zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b"}, - {file = "zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00"}, - {file = "zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64"}, - {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea"}, - {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb"}, - {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a"}, - {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512"}, - {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa"}, - {file = "zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd"}, - {file = "zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01"}, - {file = "zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9"}, - {file = "zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94"}, - {file = "zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1"}, - {file = "zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f"}, - {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea"}, - {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e"}, - {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551"}, - {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98"}, - {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf"}, - {file = "zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09"}, - {file = "zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5"}, - {file = "zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049"}, - {file = "zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3"}, - {file = "zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f"}, - {file = "zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c"}, - {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439"}, - {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043"}, - {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859"}, - {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0"}, - {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7"}, - {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2"}, - {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344"}, - {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c"}, - {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088"}, - {file = "zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12"}, - {file = "zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2"}, - {file = "zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d"}, - {file = "zstandard-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0"}, - {file = "zstandard-0.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2"}, - {file = "zstandard-0.25.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df"}, - {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53"}, - {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3"}, - {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362"}, - {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388"}, - {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27"}, - {file = "zstandard-0.25.0-cp39-cp39-win32.whl", hash = "sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649"}, - {file = "zstandard-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860"}, - {file = "zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b"}, -] - -[package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] - -[metadata] -lock-version = "2.1" -python-versions = ">=3.10,<4.0" -content-hash = "fb12d670f161c15a324be9497a9dd141dce0f4262ce1bedd1949cb45ef86cc51" diff --git a/pyproject.toml b/pyproject.toml index e564e59bd..1467ef436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,45 +1,50 @@ -[tool.poetry] +[project] name = "langfuse" - version = "4.0.3" description = "A client library for accessing langfuse" -authors = ["langfuse "] -license = "MIT" readme = "README.md" +authors = [{ name = "langfuse", email = "developers@langfuse.com" }] +license = "MIT" +license-files = ["LICENSE"] +requires-python = ">=3.10,<4.0" +dependencies = [ + "httpx>=0.15.4,<1.0", + "pydantic>=2,<3", + "backoff>=1.10.0", + "wrapt>=1.14,<2", + "packaging>=23.2,<27.0", + "opentelemetry-api>=1.33.1,<2", + "opentelemetry-sdk>=1.33.1,<2", + "opentelemetry-exporter-otlp-proto-http>=1.33.1,<2", +] -[tool.poetry.dependencies] -python = ">=3.10,<4.0" -httpx = ">=0.15.4,<1.0" -pydantic = "^2" -backoff = ">=1.10.0" -openai = { version = ">=0.27.8", optional = true } -wrapt = "^1.14" -packaging = ">=23.2,<27.0" -opentelemetry-api = "^1.33.1" -opentelemetry-sdk = "^1.33.1" -opentelemetry-exporter-otlp-proto-http = "^1.33.1" - -[tool.poetry.group.dev.dependencies] -pytest = ">=7.4,<9.0" -pytest-timeout = "^2.1.0" -pytest-xdist = "^3.3.1" -pre-commit = "^3.2.2" -pytest-asyncio = ">=0.21.1,<1.2.0" -pytest-httpserver = "^1.0.8" -ruff = "^0.15.2" -mypy = "^1.0.0" -langchain-openai = ">=0.0.5,<0.4" -langchain = ">=1" -langgraph = ">=1" -autoevals = "^0.0.130" -opentelemetry-instrumentation-threading = "^0.59b0" - -[tool.poetry.group.docs.dependencies] -pdoc = "^15.0.4" +[dependency-groups] +dev = [ + "pytest>=7.4,<9.0", + "pytest-timeout>=2.1.0,<3", + "pytest-xdist>=3.3.1,<4", + "pre-commit>=3.2.2,<4", + "pytest-asyncio>=0.21.1,<1.2.0", + "pytest-httpserver>=1.0.8,<2", + "ruff>=0.15.2,<0.16", + "mypy>=1.0.0,<2", + "openai>=0.27.8", + "langchain-openai>=0.0.5,<0.4", + "langchain>=1,<2", + "langgraph>=1,<2", + "autoevals>=0.0.130,<0.1", + "opentelemetry-instrumentation-threading>=0.59b0,<1", +] +docs = [ + "pdoc>=15.0.4,<16", +] [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["uv_build>=0.11.2,<0.12.0"] +build-backend = "uv_build" + +[tool.uv.build-backend] +module-root = "" [tool.pytest.ini_options] log_cli = true diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 000000000..883c7ee91 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,7 @@ +from importlib.metadata import version + +import langfuse + + +def test_package_version_matches_distribution_metadata(): + assert langfuse.__version__ == version("langfuse") diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..3cc22adf7 --- /dev/null +++ b/uv.lock @@ -0,0 +1,2450 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <4.0" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "autoevals" +version = "0.0.130" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chevron" }, + { name = "jsonschema" }, + { name = "polyleven" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/92/d80c8a7a34a2b64927ba0844fc6b71cf0c7224c4244d87f618cd3043da06/autoevals-0.0.130.tar.gz", hash = "sha256:92f87ab95a575b56d9d7377e6f1399932d09180d2f3a8266b4f693f46f49b86d", size = 51839, upload-time = "2025-09-08T05:30:01.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/a0/f59dd73e8582c59672cf1f4e5f3ec60d1ee312f8f2a56ae54af5293173c7/autoevals-0.0.130-py3-none-any.whl", hash = "sha256:ffb7b3a21070d2a4e593bb118180c04e43531e608bffd854624377bd857ceec0", size = 56034, upload-time = "2025-09-08T05:29:59.908Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" }, + { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" }, + { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" }, + { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" }, + { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +] + +[[package]] +name = "chevron" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/1f/ca74b65b19798895d63a6e92874162f44233467c9e7c1ed8afd19016ebe9/chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf", size = 11440, upload-time = "2021-01-02T22:47:59.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/93/342cc62a70ab727e093ed98e02a725d85b746345f05d2b5e5034649f4ec8/chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443", size = 11595, upload-time = "2021-01-02T22:47:57.847Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "filelock" +version = "3.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.73.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506, upload-time = "2026-03-26T22:17:38.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556, upload-time = "2026-03-26T22:15:58.455Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "identify" +version = "2.6.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, + { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, + { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, + { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, + { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, + { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "langchain" +version = "1.2.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/e5/56fdeedaa0ef1be3c53721d382d9e21c63930179567361610ea6102c04ea/langchain-1.2.13.tar.gz", hash = "sha256:d566ef67c8287e7f2e2df3c99bf3953a6beefd2a75a97fe56ecce905e21f3ef4", size = 573819, upload-time = "2026-03-19T17:16:07.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1d/a509af07535d8f4621d77f3ba5ec846ee6d52c59d2239e1385ec3b29bf92/langchain-1.2.13-py3-none-any.whl", hash = "sha256:37d4526ac4b0cdd3d7706a6366124c30dc0771bf5340865b37cdc99d5e5ad9b1", size = 112488, upload-time = "2026-03-19T17:16:06.134Z" }, +] + +[[package]] +name = "langchain-core" +version = "1.2.23" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/47/a5f21b651e9cbd7a26c3e5809336d10a0be94ef7bdf6bea47f2ad9fff1a8/langchain_core-1.2.23.tar.gz", hash = "sha256:fdec64f90cfea25317e88d9803c44684af1f4e30dec4e58320dd7393bb0f0785", size = 841684, upload-time = "2026-03-27T23:28:14.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/5a/6ff2d76618e4cac531ea51d4ef44c6add36575a84c3f0f8877aee68c951a/langchain_core-1.2.23-py3-none-any.whl", hash = "sha256:70866dfc5275b7840ce272ff70f0ff216c8666ab25dc1b41964a4ef58c02a3ff", size = 506709, upload-time = "2026-03-27T23:28:13.372Z" }, +] + +[[package]] +name = "langchain-openai" +version = "0.3.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/23/91461c6c2b1c8ceeadd6fefe453a03b1537afeff4ace3c118d7e7659f5a7/langchain_openai-0.3.34.tar.gz", hash = "sha256:57916d462be5b8fd19e5cb2f00d4e5cf0465266a292d583de2fc693a55ba190e", size = 785924, upload-time = "2025-10-01T15:18:40.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/94/132aff5a1ed4ebfe7b4d3e4dbf30b9f70ce2bd2ac3470d0c0e742845afb1/langchain_openai-0.3.34-py3-none-any.whl", hash = "sha256:08d61d68a6d869c70d542171e149b9065668dedfc4fafcd4de8aeb5b933030a9", size = 75605, upload-time = "2025-10-01T15:18:39.415Z" }, +] + +[[package]] +name = "langfuse" +version = "4.0.3" +source = { editable = "." } +dependencies = [ + { name = "backoff" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "wrapt" }, +] + +[package.dev-dependencies] +dev = [ + { name = "autoevals" }, + { name = "langchain" }, + { name = "langchain-openai" }, + { name = "langgraph" }, + { name = "mypy" }, + { name = "openai" }, + { name = "opentelemetry-instrumentation-threading" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-httpserver" }, + { name = "pytest-timeout" }, + { name = "pytest-xdist" }, + { name = "ruff" }, +] +docs = [ + { name = "pdoc" }, +] + +[package.metadata] +requires-dist = [ + { name = "backoff", specifier = ">=1.10.0" }, + { name = "httpx", specifier = ">=0.15.4,<1.0" }, + { name = "opentelemetry-api", specifier = ">=1.33.1,<2" }, + { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.33.1,<2" }, + { name = "opentelemetry-sdk", specifier = ">=1.33.1,<2" }, + { name = "packaging", specifier = ">=23.2,<27.0" }, + { name = "pydantic", specifier = ">=2,<3" }, + { name = "wrapt", specifier = ">=1.14,<2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "autoevals", specifier = ">=0.0.130,<0.1" }, + { name = "langchain", specifier = ">=1,<2" }, + { name = "langchain-openai", specifier = ">=0.0.5,<0.4" }, + { name = "langgraph", specifier = ">=1,<2" }, + { name = "mypy", specifier = ">=1.0.0,<2" }, + { name = "openai", specifier = ">=0.27.8" }, + { name = "opentelemetry-instrumentation-threading", specifier = ">=0.59b0,<1" }, + { name = "pre-commit", specifier = ">=3.2.2,<4" }, + { name = "pytest", specifier = ">=7.4,<9.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.1,<1.2.0" }, + { name = "pytest-httpserver", specifier = ">=1.0.8,<2" }, + { name = "pytest-timeout", specifier = ">=2.1.0,<3" }, + { name = "pytest-xdist", specifier = ">=3.3.1,<4" }, + { name = "ruff", specifier = ">=0.15.2,<0.16" }, +] +docs = [{ name = "pdoc", specifier = ">=15.0.4,<16" }] + +[[package]] +name = "langgraph" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/b2/e7db624e8b0ee063ecfbf7acc09467c0836a05914a78e819dfb3744a0fac/langgraph-1.1.3.tar.gz", hash = "sha256:ee496c297a9c93b38d8560be15cbb918110f49077d83abd14976cb13ac3b3370", size = 545120, upload-time = "2026-03-18T23:42:58.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/f7/221cc479e95e03e260496616e5ce6fb50c1ea01472e3a5bc481a9b8a2f83/langgraph-1.1.3-py3-none-any.whl", hash = "sha256:57cd6964ebab41cbd211f222293a2352404e55f8b2312cecde05e8753739b546", size = 168149, upload-time = "2026-03-18T23:42:56.967Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/012f0e0f5c9fd26f92bdc9d244756ad673c428230156ef668e6ec7c18cee/langgraph_sdk-0.3.12.tar.gz", hash = "sha256:c9c9ec22b3c0fcd352e2b8f32a815164f69446b8648ca22606329f4ff4c59a71", size = 194932, upload-time = "2026-03-18T22:15:54.592Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/4d/4f796e86b03878ab20d9b30aaed1ad459eda71a5c5b67f7cfe712f3548f2/langgraph_sdk-0.3.12-py3-none-any.whl", hash = "sha256:44323804965d6ec2a07127b3cf08a0428ea6deaeb172c2d478d5cd25540e3327", size = 95834, upload-time = "2026-03-18T22:15:53.545Z" }, +] + +[[package]] +name = "langsmith" +version = "0.7.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "uuid-utils" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923, upload-time = "2026-03-19T22:45:23.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927, upload-time = "2026-03-19T22:45:21.603Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/5f/63f5fa395c7a8a93558c0904ba8f1c8d1b997ca6a3de61bc7659970d66bf/librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc", size = 65697, upload-time = "2026-02-17T16:11:06.903Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e0/0472cf37267b5920eff2f292ccfaede1886288ce35b7f3203d8de00abfe6/librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7", size = 68376, upload-time = "2026-02-17T16:11:08.395Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8bd1359fdcd27ab897cd5963294fa4a7c83b20a8564678e4fd12157e56a5/librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6", size = 197084, upload-time = "2026-02-17T16:11:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fe/163e33fdd091d0c2b102f8a60cc0a61fd730ad44e32617cd161e7cd67a01/librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0", size = 207337, upload-time = "2026-02-17T16:11:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/f85130582f05dcf0c8902f3d629270231d2f4afdfc567f8305a952ac7f14/librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b", size = 219980, upload-time = "2026-02-17T16:11:12.499Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/cb5e4d03659e043a26c74e08206412ac9a3742f0477d96f9761a55313b5f/librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6", size = 212921, upload-time = "2026-02-17T16:11:14.484Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/a3a01e4240579c30f3487f6fed01eb4bc8ef0616da5b4ebac27ca19775f3/librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71", size = 221381, upload-time = "2026-02-17T16:11:17.459Z" }, + { url = "https://files.pythonhosted.org/packages/08/b0/fc2d54b4b1c6fb81e77288ff31ff25a2c1e62eaef4424a984f228839717b/librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7", size = 216714, upload-time = "2026-02-17T16:11:19.197Z" }, + { url = "https://files.pythonhosted.org/packages/96/96/85daa73ffbd87e1fb287d7af6553ada66bf25a2a6b0de4764344a05469f6/librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05", size = 214777, upload-time = "2026-02-17T16:11:20.443Z" }, + { url = "https://files.pythonhosted.org/packages/12/9c/c3aa7a2360383f4bf4f04d98195f2739a579128720c603f4807f006a4225/librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891", size = 237398, upload-time = "2026-02-17T16:11:22.083Z" }, + { url = "https://files.pythonhosted.org/packages/61/19/d350ea89e5274665185dabc4bbb9c3536c3411f862881d316c8b8e00eb66/librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7", size = 54285, upload-time = "2026-02-17T16:11:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d6/45d587d3d41c112e9543a0093d883eb57a24a03e41561c127818aa2a6bcc/librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2", size = 61352, upload-time = "2026-02-17T16:11:24.207Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd", size = 65315, upload-time = "2026-02-17T16:11:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965", size = 68021, upload-time = "2026-02-17T16:11:26.129Z" }, + { url = "https://files.pythonhosted.org/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da", size = 194500, upload-time = "2026-02-17T16:11:27.177Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0", size = 205622, upload-time = "2026-02-17T16:11:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e", size = 218304, upload-time = "2026-02-17T16:11:29.344Z" }, + { url = "https://files.pythonhosted.org/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3", size = 211493, upload-time = "2026-02-17T16:11:30.445Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac", size = 219129, upload-time = "2026-02-17T16:11:32.021Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596", size = 213113, upload-time = "2026-02-17T16:11:33.192Z" }, + { url = "https://files.pythonhosted.org/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99", size = 212269, upload-time = "2026-02-17T16:11:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe", size = 234673, upload-time = "2026-02-17T16:11:36.063Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb", size = 54597, upload-time = "2026-02-17T16:11:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b", size = 61733, upload-time = "2026-02-17T16:11:38.691Z" }, + { url = "https://files.pythonhosted.org/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9", size = 52273, upload-time = "2026-02-17T16:11:40.308Z" }, + { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, + { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, + { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, + { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "openai" +version = "2.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/15/52580c8fbc16d0675d516e8749806eda679b16de1e4434ea06fb6feaa610/openai-2.30.0.tar.gz", hash = "sha256:92f7661c990bda4b22a941806c83eabe4896c3094465030dd882a71abe80c885", size = 676084, upload-time = "2026-03-25T22:08:59.96Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/9e/5bfa2270f902d5b92ab7d41ce0475b8630572e71e349b2a4996d14bdda93/openai-2.30.0-py3-none-any.whl", hash = "sha256:9a5ae616888eb2748ec5e0c5b955a51592e0b201a11f4262db920f2a78c5231d", size = 1146656, upload-time = "2026-03-25T22:08:58.2Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-threading" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/8f/8dedba66100cda58af057926449a5e58e6c008bec02bc2746c03c3d85dcd/opentelemetry_instrumentation_threading-0.61b0.tar.gz", hash = "sha256:38e0263c692d15a7a458b3fa0286d29290448fa4ac4c63045edac438c6113433", size = 9163, upload-time = "2026-03-04T14:20:50.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/77/c06d960aede1a014812aa4fafde0ae546d790f46416fbeafa2b32095aae3/opentelemetry_instrumentation_threading-0.61b0-py3-none-any.whl", hash = "sha256:735f4a1dc964202fc8aff475efc12bb64e6566f22dff52d5cb5de864b3fe1a70", size = 9337, upload-time = "2026-03-04T14:19:57.983Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1a/a373746fa6d0e116dd9e54371a7b54622c44d12296d5d0f3ad5e3ff33490/orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174", size = 229140, upload-time = "2026-02-02T15:37:06.082Z" }, + { url = "https://files.pythonhosted.org/packages/52/a2/fa129e749d500f9b183e8a3446a193818a25f60261e9ce143ad61e975208/orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67", size = 128670, upload-time = "2026-02-02T15:37:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/08/93/1e82011cd1e0bd051ef9d35bed1aa7fb4ea1f0a055dc2c841b46b43a9ebd/orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11", size = 123832, upload-time = "2026-02-02T15:37:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/a26b431ef962c7d55736674dddade876822f3e33223c1f47a36879350d04/orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc", size = 129171, upload-time = "2026-02-02T15:37:11.112Z" }, + { url = "https://files.pythonhosted.org/packages/a7/19/f47819b84a580f490da260c3ee9ade214cf4cf78ac9ce8c1c758f80fdfc9/orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16", size = 141967, upload-time = "2026-02-02T15:37:12.282Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cd/37ece39a0777ba077fdcdbe4cccae3be8ed00290c14bf8afdc548befc260/orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222", size = 130991, upload-time = "2026-02-02T15:37:13.465Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ed/f2b5d66aa9b6b5c02ff5f120efc7b38c7c4962b21e6be0f00fd99a5c348e/orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa", size = 133674, upload-time = "2026-02-02T15:37:14.694Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6e/baa83e68d1aa09fa8c3e5b2c087d01d0a0bd45256de719ed7bc22c07052d/orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e", size = 138722, upload-time = "2026-02-02T15:37:16.501Z" }, + { url = "https://files.pythonhosted.org/packages/0c/47/7f8ef4963b772cd56999b535e553f7eb5cd27e9dd6c049baee6f18bfa05d/orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2", size = 409056, upload-time = "2026-02-02T15:37:17.895Z" }, + { url = "https://files.pythonhosted.org/packages/38/eb/2df104dd2244b3618f25325a656f85cc3277f74bbd91224752410a78f3c7/orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c", size = 144196, upload-time = "2026-02-02T15:37:19.349Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2a/ee41de0aa3a6686598661eae2b4ebdff1340c65bfb17fcff8b87138aab21/orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f", size = 134979, upload-time = "2026-02-02T15:37:20.906Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/92fc5d3d402b87a8b28277a9ed35386218a6a5287c7fe5ee9b9f02c53fb2/orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de", size = 127968, upload-time = "2026-02-02T15:37:23.178Z" }, + { url = "https://files.pythonhosted.org/packages/07/29/a576bf36d73d60df06904d3844a9df08e25d59eba64363aaf8ec2f9bff41/orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993", size = 125128, upload-time = "2026-02-02T15:37:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/37/02/da6cb01fc6087048d7f61522c327edf4250f1683a58a839fdcc435746dd5/orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c", size = 228664, upload-time = "2026-02-02T15:37:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c2/5885e7a5881dba9a9af51bc564e8967225a642b3e03d089289a35054e749/orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b", size = 125344, upload-time = "2026-02-02T15:37:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1d/4e7688de0a92d1caf600dfd5fb70b4c5bfff51dfa61ac555072ef2d0d32a/orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e", size = 128404, upload-time = "2026-02-02T15:37:28.108Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b2/ec04b74ae03a125db7bd69cffd014b227b7f341e3261bf75b5eb88a1aa92/orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5", size = 123677, upload-time = "2026-02-02T15:37:30.287Z" }, + { url = "https://files.pythonhosted.org/packages/4c/69/f95bdf960605f08f827f6e3291fe243d8aa9c5c9ff017a8d7232209184c3/orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62", size = 128950, upload-time = "2026-02-02T15:37:31.595Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1b/de59c57bae1d148ef298852abd31909ac3089cff370dfd4cd84cc99cbc42/orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910", size = 141756, upload-time = "2026-02-02T15:37:32.985Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9e/9decc59f4499f695f65c650f6cfa6cd4c37a3fbe8fa235a0a3614cb54386/orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b", size = 130812, upload-time = "2026-02-02T15:37:34.204Z" }, + { url = "https://files.pythonhosted.org/packages/28/e6/59f932bcabd1eac44e334fe8e3281a92eacfcb450586e1f4bde0423728d8/orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960", size = 133444, upload-time = "2026-02-02T15:37:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/f1/36/b0f05c0eaa7ca30bc965e37e6a2956b0d67adb87a9872942d3568da846ae/orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8", size = 138609, upload-time = "2026-02-02T15:37:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/b8/03/58ec7d302b8d86944c60c7b4b82975d5161fcce4c9bc8c6cb1d6741b6115/orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504", size = 408918, upload-time = "2026-02-02T15:37:38.076Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/868d65ef9a8b99be723bd510de491349618abd9f62c826cf206d962db295/orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e", size = 143998, upload-time = "2026-02-02T15:37:39.706Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/1e18e1c83afe3349f4f6dc9e14910f0ae5f82eac756d1412ea4018938535/orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561", size = 134802, upload-time = "2026-02-02T15:37:41.002Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0b/ccb7ee1a65b37e8eeb8b267dc953561d72370e85185e459616d4345bab34/orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d", size = 127828, upload-time = "2026-02-02T15:37:42.241Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/55c776dffda3f381e0f07d010a4f5f3902bf48eaba1bb7684d301acd4924/orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471", size = 124941, upload-time = "2026-02-02T15:37:43.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/424a620fa7d263b880162505fb107ef5e0afaa765b5b06a88312ac291560/orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d", size = 126245, upload-time = "2026-02-02T15:37:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/a91f70829ebccf6387c4946e0a1a109f6ba0d6a28d65f628bedfad94b890/ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657", size = 378262, upload-time = "2026-01-18T20:55:22.284Z" }, + { url = "https://files.pythonhosted.org/packages/5f/62/3698a9a0c487252b5c6a91926e5654e79e665708ea61f67a8bdeceb022bf/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163", size = 203034, upload-time = "2026-01-18T20:55:53.324Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/f716f64edc4aec2744e817660b317e2f9bb8de372338a95a96198efa1ac1/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59f5da97000c12bc2d50e988bdc8576b21f6ab4e608489879d35b2c07a8ab51a", size = 210538, upload-time = "2026-01-18T20:55:20.097Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/a436be9ce27d693d4e19fa94900028067133779f09fc45776db3f689c822/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e4459c3f27066beadb2b81ea48a076a417aafffff7df1d3c11c519190ed44f2", size = 212401, upload-time = "2026-01-18T20:55:46.447Z" }, + { url = "https://files.pythonhosted.org/packages/10/c5/cde98300fd33fee84ca71de4751b19aeeca675f0cf3c0ec4b043f40f3b76/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a1c460655d7288407ffa09065e322a7231997c0d62ce914bf3a96ad2dc6dedd", size = 387080, upload-time = "2026-01-18T20:56:00.884Z" }, + { url = "https://files.pythonhosted.org/packages/6a/31/30bf445ef827546747c10889dd254b3d84f92b591300efe4979d792f4c41/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:458e4568be13d311ef7d8877275e7ccbe06c0e01b39baaac874caaa0f46d826c", size = 482346, upload-time = "2026-01-18T20:55:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f5/e1745ddf4fa246c921b5ca253636c4c700ff768d78032f79171289159f6e/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cde5eaa6c6cbc8622db71e4a23de56828e3d876aeb6460ffbcb5b8aff91093b", size = 425178, upload-time = "2026-01-18T20:55:27.106Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a2/e6532ed7716aed03dede8df2d0d0d4150710c2122647d94b474147ccd891/ormsgpack-1.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc7a33be14c347893edbb1ceda89afbf14c467d593a5ee92c11de4f1666b4d4f", size = 117183, upload-time = "2026-01-18T20:55:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/08/8b68f24b18e69d92238aa8f258218e6dfeacf4381d9d07ab8df303f524a9/ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9", size = 378266, upload-time = "2026-01-18T20:55:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/29fc13044ecb7c153523ae0a1972269fcd613650d1fa1a9cec1044c6b666/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a", size = 203035, upload-time = "2026-01-18T20:55:30.59Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c2/00169fb25dd8f9213f5e8a549dfb73e4d592009ebc85fbbcd3e1dcac575b/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5", size = 210539, upload-time = "2026-01-18T20:55:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/543627f323ff3c73091f51d6a20db28a1a33531af30873ea90c5ac95a9b5/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181", size = 212401, upload-time = "2026-01-18T20:56:10.101Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5d/f70e2c3da414f46186659d24745483757bcc9adccb481a6eb93e2b729301/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b", size = 387082, upload-time = "2026-01-18T20:56:12.047Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d6/06e8dc920c7903e051f30934d874d4afccc9bb1c09dcaf0bc03a7de4b343/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92", size = 482346, upload-time = "2026-01-18T20:56:05.152Z" }, + { url = "https://files.pythonhosted.org/packages/66/c4/f337ac0905eed9c393ef990c54565cd33644918e0a8031fe48c098c71dbf/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a", size = 425181, upload-time = "2026-01-18T20:55:37.83Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/6d5758fabef3babdf4bbbc453738cc7de9cd3334e4c38dd5737e27b85653/ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c", size = 117182, upload-time = "2026-01-18T20:55:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/17a15549233c37e7fd054c48fe9207492e06b026dbd872b826a0b5f833b6/ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd", size = 111464, upload-time = "2026-01-18T20:55:38.811Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "pdoc" +version = "15.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/5c/e94c1ab4aa2f8a9cc29d81e1c513c6216946cb3a90957ef7115b12e9363d/pdoc-15.0.4.tar.gz", hash = "sha256:cf9680f10f5b4863381f44ef084b1903f8f356acb0d4cc6b64576ba9fb712c82", size = 155678, upload-time = "2025-06-04T17:05:49.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/2c/87250ac73ca8730b2c4e0185b573585f0b42e09562132e6c29d00b3a9bb9/pdoc-15.0.4-py3-none-any.whl", hash = "sha256:f9028e85e7bb8475b054e69bde1f6d26fc4693d25d9fa1b1ce9009bec7f7a5c4", size = 145978, upload-time = "2025-06-04T17:05:48.473Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "polyleven" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/c7/e0b3bbe72e0003e5d02726e0d406ea47d523a2aec9c41d831817a8e0bce1/polyleven-0.11.0.tar.gz", hash = "sha256:d74d348387cf340051711c0dd6af993b4c264daa78470098de16f4a2b725785c", size = 6407, upload-time = "2026-02-09T09:41:49.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/6c/34c6189c80adf7575fb2daee38c4b836c154e416ab3c14d17afa7f88b9c3/polyleven-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccf87f6ac8d76aa4c48a4828becc0c19fd1589b14b20affe23e5e012be4fa64f", size = 7421, upload-time = "2026-02-09T09:40:33.243Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/b20d3c9f9b6bded43a0388037044f2bcb1add20fa9a758d1144d79e09d10/polyleven-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1a02e3f0acfd1164cbaea25192398bc943ee9b93b9883a1fba9b2613d3616b0", size = 7514, upload-time = "2026-02-09T09:40:34.514Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d8/60290fd8d8298671edb4ce221d8ee4d81156b3c21b1154a615da7ea8e57d/polyleven-0.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6526d2516b439065864722069de6fcc418a4135696990dad66b81ddb18863bd9", size = 19556, upload-time = "2026-02-09T09:40:35.868Z" }, + { url = "https://files.pythonhosted.org/packages/c1/cd/17f1f6009a344c18f4213edcc97a9e7dae22e9a26e722aae10052378a43e/polyleven-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65fc01fe6cfe287f2f20170b35687a436ab36b882db568a55d81d6e0acd8379d", size = 20303, upload-time = "2026-02-09T09:40:36.99Z" }, + { url = "https://files.pythonhosted.org/packages/4e/18/30c7da8056adc4b9a81775f5b1810a2c9b6cc87fc34c8cf4c0280d28cab6/polyleven-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4b8e5ac8faecc6daa7b3d325436a3f23f8c33dec7bfca5d22df3fbe00f92ddd9", size = 19565, upload-time = "2026-02-09T09:40:38.471Z" }, + { url = "https://files.pythonhosted.org/packages/de/14/86e9c33ff9fda84297556373a6376100cbe9bb5d917fc3421dce4ada441b/polyleven-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f17007b07fde292ad33ec43a3ae8febe27a5bd92462b920736fd81d774fce", size = 19365, upload-time = "2026-02-09T09:40:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/be/18/1341f7860bbe2287f6cb8a540c3435c504cfc58659b67106ab60f695175e/polyleven-0.11.0-cp310-cp310-win32.whl", hash = "sha256:cae70197d545a09bfab8d7e506eed66ef314fa6c4e7a5e2c402c2febc31db74b", size = 11672, upload-time = "2026-02-09T09:40:40.822Z" }, + { url = "https://files.pythonhosted.org/packages/6d/09/ed2cb3dbec7a925d80107516b06dfe10dd368c6abfb43765df6feb6cd551/polyleven-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:bf47079b6dc62e6af2bd6ecb45a6087efd9a27b61666b98d0326c246a22ea991", size = 10828, upload-time = "2026-02-09T09:40:42.224Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/cb74c2ffe4e35935d80ef6f180f9e0987b8000917d52050a3563b32e1e73/polyleven-0.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:4c78b4d3e7d7b74315d5422178118963374c0cf3d7a9532a955f446ed365320a", size = 9389, upload-time = "2026-02-09T09:40:43.172Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7a/27ea9a78b617ddb14c2f5d2416df2fbf07fa5e52685f2968686a0308c8af/polyleven-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a28860fe33a7f907bc5f86e55a0b9faea80047d1677fa23b4d6c631ccf91ef2f", size = 7420, upload-time = "2026-02-09T09:40:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9c/fea309d41502aa5a344a6d4d6e5b8bdabb1df1e28f1af52bb53180f6c956/polyleven-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:47a3fb5b8cb60f647d2832d38b7d87cda27da8622b27c1292bceb9a04954c189", size = 7514, upload-time = "2026-02-09T09:40:46.44Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/c7d3bb6c66050304c3fe3cae1a716f62fea947ac3f14d02ef71e24422f76/polyleven-0.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:209fa669ca23ac453a7e9fbf07695350d5cbe61d71a6226b861757ccab28e664", size = 20887, upload-time = "2026-02-09T09:40:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/77/1b/aeaf38075c7e0225fd7ba89db3bd11c0e65b50907242d82d57c4804941d9/polyleven-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3bfce4689b6aaacf7c5296b8ed11ada07ccf046a01097ba1681e10f9caabbf6f", size = 21376, upload-time = "2026-02-09T09:40:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/bd/96/10f01f8ab883a51ef7bed610933ba88b7cf5b0a0e3fd059c94f1db8414d4/polyleven-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:83e59c8590a06ea6a959a3c55e6d28544b8d11a51aac2a318c1b74f92575dd28", size = 20436, upload-time = "2026-02-09T09:40:50.059Z" }, + { url = "https://files.pythonhosted.org/packages/b1/4e/5cedf4cfde32eddab388435463a0f8c2e322449c61ef4765db62ca7c0d13/polyleven-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2eb8f6778f3073dce041805a09f0753cc441b0219253d7b933aad234a954f30a", size = 20685, upload-time = "2026-02-09T09:40:51.03Z" }, + { url = "https://files.pythonhosted.org/packages/a0/40/d88e50b60d0a731d9fb7e71268a91fcf8e290eaa5c043715d8f6ad158fe2/polyleven-0.11.0-cp311-cp311-win32.whl", hash = "sha256:248b9f645d8c6e337091498ed5c7d4a796d9d51df98458be25b1d76d962954e2", size = 11613, upload-time = "2026-02-09T09:40:52.468Z" }, + { url = "https://files.pythonhosted.org/packages/33/67/a52aeeb5200ea4c6d1642cd9e86827feee619e56792d1795465532a9d6f8/polyleven-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:5c9ccb2f327d49a7566b0192e0d426f7772b38e247dc4e809c0b1cdf23e2ecc2", size = 10814, upload-time = "2026-02-09T09:40:53.355Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e1/857fa37a1d4cca74cb2a144cd2962d265fd2d705f971c146d6ee6cdab546/polyleven-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:9b97b9260730deda4cdd5878fd6ce128b970497da0fbefeeacc0b1ed4c59ebb7", size = 9420, upload-time = "2026-02-09T09:40:54.59Z" }, + { url = "https://files.pythonhosted.org/packages/9b/31/a9b7aa80589d54bde7d894a9fb118a766833def3ded11739e70b1f19d951/polyleven-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a33728df4708c9370f5e65fdb8de7e15f01d5ae8530eedf507d182fe63afb0e", size = 7437, upload-time = "2026-02-09T09:40:55.459Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5e/71ee3cb252fd6abdf19dd40c11f87ad3adffa06cc0b8098b98ec355573a4/polyleven-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58c11ce44466f6d833fd90f77ccd0c44accce41c9e80dea4c2817d5c124a61d5", size = 7510, upload-time = "2026-02-09T09:40:56.296Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1b/4c41947cfb2f7c4348d60d53d45418e47c305e33b1ce78218b84d636fca8/polyleven-0.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ce2e782f8fae812c7ad960c4fd17a58ada183b89ae220cacfe6b3234179872f4", size = 20982, upload-time = "2026-02-09T09:40:57.152Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/07e4f90873e4c0c764f6081ad44c17308d7d18976284d80bca47c98e4282/polyleven-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a472fccba89ffb44b10481760c7351c79855b0ff654ec9d28966bfb111a71748", size = 21476, upload-time = "2026-02-09T09:41:00.3Z" }, + { url = "https://files.pythonhosted.org/packages/67/26/31871030852e62e705d060c1dafaddd2f9a4b3664a8a6866ad7521e7e07f/polyleven-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:758c5fcb9d8556720fb51c1de5f3a7b39bb8dce9510a1f40e5f287951b901010", size = 20497, upload-time = "2026-02-09T09:41:01.252Z" }, + { url = "https://files.pythonhosted.org/packages/71/8a/8e1b60f5fc905c0437a063414eee58fcc775b8ae6229e85439b6c477b331/polyleven-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:367faf0e1898c79624f46a894ebe5b69bf1782318ec3e3331676ce5b24352882", size = 20768, upload-time = "2026-02-09T09:41:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/42/00/7f9bab45279828b9d70a0674d92fc78e858dcbfa79b1e0669f93b039249e/polyleven-0.11.0-cp312-cp312-win32.whl", hash = "sha256:71bbb17919548d4e162444c918b1acf864f84150197087c757975606cbb99e43", size = 11625, upload-time = "2026-02-09T09:41:03.189Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/47a019878909f80526567347379ba73aff114c5ee6d6c6efb7a0b6d4573b/polyleven-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:e7b6c8cfa13114bc2b17b51503a4db0cbc358c3c96197d6d7283bd686c0fd8fb", size = 10834, upload-time = "2026-02-09T09:41:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/3e/40/4e80a66231052693328fc866a932e07b636cf8bb7ddf0eb54aa475f792bf/polyleven-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:561f028c9535223c78cd58f6b546dd15ce69a6e268e651ae1377644845fae639", size = 9424, upload-time = "2026-02-09T09:41:05.225Z" }, + { url = "https://files.pythonhosted.org/packages/ce/16/5aec69609adc373f10087eb69b0b9d177ae721632715a86348b429030514/polyleven-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cb8ed97b536f9aada3ad45169ee7768c426498bf3fa608a4eabd055dfef795e", size = 7425, upload-time = "2026-02-09T09:41:06.542Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5b/0542c723aa83833a5090114bc4e5a8e60293873fe60ee8221a5888d87370/polyleven-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f975ab8cb81fd8eb5a647a3cefb0bb80bc307920a9307f66ab4019d88370ed2", size = 7505, upload-time = "2026-02-09T09:41:07.445Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8d/c317217734a5bd2011f1128c1a9056477a5148d8d95527fcab2fe3955876/polyleven-0.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16986fd58911d6075b5f63ea001197141145b7a6df48bc4ce4530e79227e74a2", size = 21035, upload-time = "2026-02-09T09:41:08.32Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/8a3e6e4a68dbd9de564fd3d16eee90e3f807a4380fd7192f40af4be47175/polyleven-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6a814629cc0468f9800b1333414a3be08fda9c5ce6b63e97154a9d21732e590", size = 21509, upload-time = "2026-02-09T09:41:09.285Z" }, + { url = "https://files.pythonhosted.org/packages/6b/da/4097998bea845f0b3a67112200aa08c19d4da0a17d761b35484d695c21e2/polyleven-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:88a35ec93ec3d81a7347fd49db314a914798a144dca3d22946d18bba9b597dec", size = 20536, upload-time = "2026-02-09T09:41:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/a1/71/67b7679ede99589ec749290d938693b87cdb6bb327b062c46d2129a5e6ec/polyleven-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:50bb7d68b790194d552ee1256a02e205486b27eb22ab333eeb0003e0271c4846", size = 20775, upload-time = "2026-02-09T09:41:11.692Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3e/6f7fad4fee748ba365cb3e1ba2e061a74e18d987eb554ead4757127df2ab/polyleven-0.11.0-cp313-cp313-win32.whl", hash = "sha256:ce264f6a9daa3265299d8ffcb180d8256517a8d9235613a3b267172da0bc1e06", size = 11629, upload-time = "2026-02-09T09:41:12.652Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cc/4877913dec8fb4f968a070c894254db5811b62128d3a69b05bcd1305b5c3/polyleven-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4648732c8ad3955c8d7b1aa015d92936a150475aaa97ce704fe0c8e7fa7e0c4f", size = 10841, upload-time = "2026-02-09T09:41:13.682Z" }, + { url = "https://files.pythonhosted.org/packages/59/e2/039cc477ce73d6184e12cf6341ac200bc9f4c5428254c399015ec30392e1/polyleven-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:166f6c9b161c6af92ff201c734d6437bc7ef74a32dab306c5d47a0bdb7a82d9f", size = 9424, upload-time = "2026-02-09T09:41:14.545Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cf/a02d74f965127adb6a8fbd5030e2c98335ef2f8e7452b12a882883b2053a/polyleven-0.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3c18b8e44e5d04f1ffa7d41eb68da553833ab8663b7cfb1a505d85676db5c797", size = 7482, upload-time = "2026-02-09T09:41:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/fe/74/dfa9e9891cd85e679f230c5e740cba11b0bb11bd9fb298657ccf048ff70e/polyleven-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7ab547adc0ac72a2852d37337a4a839d4e2f713940b0e8a944d45c528e5e6538", size = 7508, upload-time = "2026-02-09T09:41:16.365Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ef/399ae8d21f7b348514b7ad3bd7b9d530bf195fb0a8ec63cf7af7d17a4071/polyleven-0.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5808f62874187dfd4e30de5dd5f42a660562ec95a87cc64d5455ba0f4be8f175", size = 21056, upload-time = "2026-02-09T09:41:17.226Z" }, + { url = "https://files.pythonhosted.org/packages/21/60/7eb97286a6171dd794a0e5b261175e8bfeb99a2b566bd9b8848ebc97f6df/polyleven-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9deb75346b4177d5e69496791e6156f705d9059961ce8f9520a0dc96532f10f2", size = 21535, upload-time = "2026-02-09T09:41:18.137Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/6fa59257c2138e33a858f10236a2a6b381b87f61251c1df468be7c666338/polyleven-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ef28c4c6cdc71a32f0478772d2f07b2cd412fe7950182033b1c36c8a481b0834", size = 20560, upload-time = "2026-02-09T09:41:19.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2d/85be9c91d05cb0127586640108f3110f6a3a98c9478f84713d4771c49761/polyleven-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94832ff5d04022ba6038c2ca0c9ea6906330cde3a3b1761739d772647d01da33", size = 20814, upload-time = "2026-02-09T09:41:20.001Z" }, + { url = "https://files.pythonhosted.org/packages/da/91/5a99ae6cf16ff55a94c5686871ed20b816ad1690f823494c76dc3ce0f54b/polyleven-0.11.0-cp314-cp314-win32.whl", hash = "sha256:e6182ea6142904ea50cf82e2955d922156b5fcf9a8279925f312961f16710a58", size = 11966, upload-time = "2026-02-09T09:41:20.946Z" }, + { url = "https://files.pythonhosted.org/packages/48/ec/9c6fcdeb1dd436523f8e2275407f588d6a66a524d7a793f554957373769c/polyleven-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:bf82bb8601582da8f2248293c1e6f4cce2025c79fd64fccddf67dd8538655b55", size = 11100, upload-time = "2026-02-09T09:41:21.863Z" }, + { url = "https://files.pythonhosted.org/packages/42/7f/1e59881a56a4963b4546c7b558ab7979daddff586001f18b80f1f66cece9/polyleven-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:45487a1e4a8415e4ed45e6720b2a3ad9d240336f7afa136a625b8f802a1880c2", size = 9624, upload-time = "2026-02-09T09:41:22.749Z" }, + { url = "https://files.pythonhosted.org/packages/47/5a/5eaa75427f17d4cdf8e2139988a3ec6b841b6e077ebc1fccb754c1f8b55e/polyleven-0.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c518ced3e7c05de4efbd12fd7b61d6d574eb170f431e0415689d9f143fe552ee", size = 7490, upload-time = "2026-02-09T09:41:23.677Z" }, + { url = "https://files.pythonhosted.org/packages/50/47/5dd5fa13d315e0d5dc3e41bbaa16306ea56e74929ad29df54d5c24a84dcc/polyleven-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa49732cdecd985241db9f78d5fdba7170ba6375d2bf9ad040b05127dc96b877", size = 7514, upload-time = "2026-02-09T09:41:24.55Z" }, + { url = "https://files.pythonhosted.org/packages/75/aa/838f1bc632144f4f5820b9dbd31e0c64de41a7b0970b5cbe6fc02746090f/polyleven-0.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2aada9dd04e84389d90790f359447447a499d6d86807697d80732ed45547a43", size = 21123, upload-time = "2026-02-09T09:41:25.401Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/d6f32263b863dfffeed9a67e80b53476cd0089f202b0510a80eb07f7425b/polyleven-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94311ee39e2db957415eacb36b96ae26dcc427c260465324de45fb8c870d4661", size = 21627, upload-time = "2026-02-09T09:41:27.219Z" }, + { url = "https://files.pythonhosted.org/packages/ae/68/4dee05a4217a3eb1f85cbc915f5fa269d79b86d2a8384be68bcd21de37cc/polyleven-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:45cfb234fece0c9df73276788fa529a25f91abf97dd0d9aed4f1b713b6d530e3", size = 20635, upload-time = "2026-02-09T09:41:28.137Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c2/8486bdaebf47e6b764e8be227a7d2898463f2b4d91443ecdeee9ebeca6bc/polyleven-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aaed455f498172769fd88f83c27bb8f43e0583d7b27d6b343154d471ec2145e", size = 20870, upload-time = "2026-02-09T09:41:29.07Z" }, + { url = "https://files.pythonhosted.org/packages/b3/13/b827188b55108bd816110a6f60b78aee0db045a98bf7b1f2e7bfb60f4039/polyleven-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2a59849c327279902e8b396666f6998234aa82aacc47abc103d93babaad46203", size = 11917, upload-time = "2026-02-09T09:41:29.997Z" }, + { url = "https://files.pythonhosted.org/packages/ab/18/c909bde1d1db7ead33329b941b0050c93cab9b811e44b49d04adb8c5f0f8/polyleven-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6ba2dcf3aff2909bbf3bdd9c1749f8de207f023fbb2c0b1d681c6bf3e78ceef1", size = 11073, upload-time = "2026-02-09T09:41:31.371Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/51f7a0fab2d65c2b6908872f26bb03bb7e2357d195f2a59aec1a27489106/polyleven-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:05207bb66da15a2dc5c530e2f5cb5f0588d0a7e79b3bd542965f9e06e3fb14fe", size = 9601, upload-time = "2026-02-09T09:41:32.235Z" }, +] + +[[package]] +name = "pre-commit" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815, upload-time = "2024-07-28T19:59:01.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643, upload-time = "2024-07-28T19:58:59.335Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/1e/2aa43805d4a320a9489d2b99f7877b69f9094c79aa0732159a1415dd6cd4/pytest_asyncio-1.1.1.tar.gz", hash = "sha256:b72d215c38e2c91dbb32f275e0b5be69602d7869910e109360e375129960a649", size = 46590, upload-time = "2025-09-12T06:36:20.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/de/aba79e9ccdb51b5d0d65c67dd857bd78b00c64723df16b9fc800d8b94ce6/pytest_asyncio-1.1.1-py3-none-any.whl", hash = "sha256:726339d30fcfde24691f589445b9b67d058b311ac632b1d704e97f20f1d878da", size = 14719, upload-time = "2025-09-12T06:36:19.726Z" }, +] + +[[package]] +name = "pytest-httpserver" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/17/ad187f46998814014f7cda309de700b87c0eb4b2e111e18bc8c819be7116/pytest_httpserver-1.1.5.tar.gz", hash = "sha256:dc3d82e1fe00e491829d8939c549bf4bd9b39a260f87113c619b9d517c2f8ff1", size = 70974, upload-time = "2026-02-14T13:27:23.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/df/0bdf90b84c6a586a9fd2b509523a3ab26b1cc1b1dba2fb62a32e4411ea9e/pytest_httpserver-1.1.5-py3-none-any.whl", hash = "sha256:ee83feb587ab652c0c6729598db2820e9048233bac8df756818b7845a1621d0a", size = 23330, upload-time = "2026-02-14T13:27:22.119Z" }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.3.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/93/5ab3e899c47fa7994e524447135a71cd121685a35c8fe35029005f8b236f/regex-2026.3.32.tar.gz", hash = "sha256:f1574566457161678297a116fa5d1556c5a4159d64c5ff7c760e7c564bf66f16", size = 415605, upload-time = "2026-03-28T21:49:22.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/87/ae29a505fdfcec85978f35d30e6de7c0ae37eaf7c287f6e88abd04be27b3/regex-2026.3.32-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:462a041d2160090553572f6bb0be417ab9bb912a08de54cb692829c871ee88c1", size = 489575, upload-time = "2026-03-28T21:45:27.167Z" }, + { url = "https://files.pythonhosted.org/packages/f9/fd/7a56c6a86213e321a309161673667091991630287d7490c5e9ec3db29607/regex-2026.3.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c6f6b027d10f84bfe65049028892b5740878edd9eae5fea0d1710b09b1d257", size = 291288, upload-time = "2026-03-28T21:45:30.886Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/ac2b481011b23f79994d4d80df03d9feccb64fbfc7bbe8dad2c3e8efc50c/regex-2026.3.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:879ae91f2928a13f01a55cfa168acedd2b02b11b4cd8b5bb9223e8cde777ca52", size = 289336, upload-time = "2026-03-28T21:45:32.631Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a2/cf7dfef7a4182e84acbe8919ce7ff50e3545007c2743219e92271b2fbc1c/regex-2026.3.32-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:887a9fa74418d74d645281ee0edcf60694053bd1bc2ebc49eb5e66bfffc6d107", size = 786358, upload-time = "2026-03-28T21:45:34.025Z" }, + { url = "https://files.pythonhosted.org/packages/fb/cb/42bfeb4597206e3171e70c973ca1d39190b48f6cda7546c25f9cb283285f/regex-2026.3.32-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d571f0b2eec3513734ea31a16ce0f7840c0b85a98e7edfa0e328ed144f9ef78f", size = 854179, upload-time = "2026-03-28T21:45:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/90/d8/9f4a7d7edffe7117de23b94696c52065b68e70267d71576d74429d598d9b/regex-2026.3.32-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ada7bd5bb6511d12177a7b00416ce55caee49fbf8c268f26b909497b534cacb", size = 898810, upload-time = "2026-03-28T21:45:37.435Z" }, + { url = "https://files.pythonhosted.org/packages/05/e6/80335c06ddf7fd7a28b97402ebe1ea4fe80a3aa162fba0f7364175f625d1/regex-2026.3.32-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:918db4e34a7ef3d0beee913fa54b34231cc3424676f1c19bdb85f01828d3cd37", size = 790605, upload-time = "2026-03-28T21:45:39.207Z" }, + { url = "https://files.pythonhosted.org/packages/38/0e/91436a89c1636090903d753d90b076784b11b8c67b79b3bde9851a45c4d7/regex-2026.3.32-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:69a847a6ffaa86e8af7b9e7037606e05a6f663deec516ad851e8e05d9908d16a", size = 786550, upload-time = "2026-03-28T21:45:40.993Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fc/ea7364b5e9abd220cebf547f2f8a42044878e9d8b02b3a652f8b807c0cbc/regex-2026.3.32-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2c8d402ea3dfe674288fe3962016affd33b5b27213d2b5db1823ffa4de524c57", size = 770223, upload-time = "2026-03-28T21:45:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/aff4ad741e914cc493e7500431cdf14e51bc808b14f1f205469d353a970b/regex-2026.3.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d6b39a2cc5625bbc4fda18919a891eab9aab934eecf83660a90ce20c53621a9a", size = 774436, upload-time = "2026-03-28T21:45:44.212Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e7/060779f504c92320f75b90caab4e57324816020986c27f57414b0a1ebcc9/regex-2026.3.32-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f7cc00089b4c21847852c0ad76fb3680f9833b855a0d30bcec94211c435bff6b", size = 849400, upload-time = "2026-03-28T21:45:46.2Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8e/6544b27f70bfd14e9c50ff5527027acc9b8f9830d352a746f843da7b0627/regex-2026.3.32-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd03e38068faeef937cc6761a250a4aaa015564bd0d61481fefcf15586d31825", size = 757934, upload-time = "2026-03-28T21:45:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6f/abf2234b3f51da1e693f13bb85e7dbb3bbdd07c04e12e0e105b9bc6006a6/regex-2026.3.32-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e006ea703d5c0f3d112b51ba18af73b58209b954acfe3d8da42eacc9a00e4be6", size = 838479, upload-time = "2026-03-28T21:45:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/db/3c/653f43c3a3643fd221bfaf61ed4a4c8f0ccc25e31a8faa8f1558a892c22c/regex-2026.3.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6980ceb5c1049d4878632f08ba0bf7234c30e741b0dc9081da0f86eca13189d3", size = 778478, upload-time = "2026-03-28T21:45:51.574Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/5e6bd702d7efc3f2a29bf65dfa46f5159653b3c6f846ddf693e1a7f9a739/regex-2026.3.32-cp310-cp310-win32.whl", hash = "sha256:6128dd0793a87287ea1d8bf16b4250dd96316c464ee15953d5b98875a284d41e", size = 266343, upload-time = "2026-03-28T21:45:53.548Z" }, + { url = "https://files.pythonhosted.org/packages/c4/89/39d04329e858956d2db1d08a10f02be8f6837c964663513ac4393158bef9/regex-2026.3.32-cp310-cp310-win_amd64.whl", hash = "sha256:5aa78c857c1731bdd9863923ffadc816d823edf475c7db6d230c28b53b7bdb5e", size = 278632, upload-time = "2026-03-28T21:45:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d8/c7e9ff3c2648408f4cda7224e195ad7a0d68724225d8d9a55eca9055504f/regex-2026.3.32-cp310-cp310-win_arm64.whl", hash = "sha256:34c905a721ddee0f84c99e3e3b59dd4a5564a6fe338222bc89dd4d4df166115c", size = 270593, upload-time = "2026-03-28T21:45:56.994Z" }, + { url = "https://files.pythonhosted.org/packages/92/c1/c68163a6ce455996db71e249a65234b1c9f79a914ea2108c6c9af9e1812a/regex-2026.3.32-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d7855f5e59fcf91d0c9f4a51dc5d8847813832a2230c3e8e35912ccf20baaa2", size = 489568, upload-time = "2026-03-28T21:45:58.791Z" }, + { url = "https://files.pythonhosted.org/packages/96/9c/0bdd47733b832b5caa11e63df14dccdb311b41ab33c1221e249af4421f8f/regex-2026.3.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:18eb45f711e942c27dbed4109830bd070d8d618e008d0db39705f3f57070a4c6", size = 291287, upload-time = "2026-03-28T21:46:00.46Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ff/1977a595f15f8dc355f9cebd875dab67f3faeca1f36b905fe53305bbcaed/regex-2026.3.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed3b8281c5d0944d939c82db4ec2300409dd69ee087f7a75a94f2e301e855fb4", size = 289325, upload-time = "2026-03-28T21:46:02.285Z" }, + { url = "https://files.pythonhosted.org/packages/0a/68/dfa21aef5af4a144702befeb5ff20ea9f9fbe40a4dfd08d56148b5b48b0a/regex-2026.3.32-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad5c53f2e8fcae9144009435ebe3d9832003508cf8935c04542a1b3b8deefa15", size = 790898, upload-time = "2026-03-28T21:46:04.079Z" }, + { url = "https://files.pythonhosted.org/packages/36/26/9424e43e0e31ac3ce1ba0e7232ee91e113a04a579c53331bc0f16a4a5bf7/regex-2026.3.32-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70c634e39c5cda0da05c93d6747fdc957599f7743543662b6dbabdd8d3ba8a96", size = 862462, upload-time = "2026-03-28T21:46:05.923Z" }, + { url = "https://files.pythonhosted.org/packages/63/a8/06573154ac891c6b55b74a88e0fb7c10081c20916b82dd0abc8cef938e13/regex-2026.3.32-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e0f6648fd48f4c73d801c55ab976cd602e2da87de99c07bff005b131f269c6a", size = 906522, upload-time = "2026-03-28T21:46:07.988Z" }, + { url = "https://files.pythonhosted.org/packages/e7/26/46673bb18448c51222c6272c850484a0092f364fae8d0315be9aa1e4baa7/regex-2026.3.32-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5e0fdb5744caf1036dec5510f543164f2144cb64932251f6dfd42fa872b7f9c", size = 798289, upload-time = "2026-03-28T21:46:09.959Z" }, + { url = "https://files.pythonhosted.org/packages/4d/cb/804f1bd5ff08687258e6a92b040aba9b770e626b8d3ba21fffdfa21db2db/regex-2026.3.32-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dab4178a0bc1ef13178832b12db7bc7f562e8f028b2b5be186e370090dc50652", size = 774823, upload-time = "2026-03-28T21:46:12.049Z" }, + { url = "https://files.pythonhosted.org/packages/e5/94/28a58258f8d822fb949c8ff87fc7e5f2a346922360ec084c193b3c95e51c/regex-2026.3.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f95bd07f301135771559101c060f558e2cf896c7df00bec050ca7f93bf11585a", size = 781381, upload-time = "2026-03-28T21:46:13.746Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f3/71e69dbe0543586a3e3532cf36e8c9b38d6d93033161a9799c1e9090eb78/regex-2026.3.32-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2dcca2bceb823c9cc610e57b86a265d7ffc30e9fe98548c609eba8bd3c0c2488", size = 855968, upload-time = "2026-03-28T21:46:15.762Z" }, + { url = "https://files.pythonhosted.org/packages/6d/99/850feec404a02b62e048718ec1b4b98b5c3848cd9ca2316d0bdb65a53f6a/regex-2026.3.32-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:567b57eb987547a23306444e4f6f85d4314f83e65c71d320d898aa7550550443", size = 762785, upload-time = "2026-03-28T21:46:17.394Z" }, + { url = "https://files.pythonhosted.org/packages/40/04/808ab0462a2d19b295a3b42134f5183692f798addfe6a8b6aa5f7c7a35b2/regex-2026.3.32-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b6acb765e7c1f2fa08ac9057a33595e26104d7d67046becae184a8f100932dd9", size = 845797, upload-time = "2026-03-28T21:46:19.269Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/8afcf0fd4bd55440b48442c86cddfe61b0d21c92d96e384c0c47d769f4c3/regex-2026.3.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1ed17104d1be7f807fdec35ec99777168dd793a09510d753f8710590ba54cdd", size = 785200, upload-time = "2026-03-28T21:46:20.939Z" }, + { url = "https://files.pythonhosted.org/packages/99/4d/23d992ab4115456fec520d6c3aae39e0e33739b244ddb39aa4102a0f7ef0/regex-2026.3.32-cp311-cp311-win32.whl", hash = "sha256:c60f1de066eb5a0fd8ee5974de4194bb1c2e7692941458807162ffbc39887303", size = 266351, upload-time = "2026-03-28T21:46:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/62/74/27c3cdb3a3fbbf67f7231b872877416ec817ae84271573d2fd14bf8723d3/regex-2026.3.32-cp311-cp311-win_amd64.whl", hash = "sha256:8fe14e24124ef41220e5992a0f09432f890037df6f93fd3d6b7a0feff2db16b2", size = 278639, upload-time = "2026-03-28T21:46:24.016Z" }, + { url = "https://files.pythonhosted.org/packages/0a/12/6a67bd509f38aec021d63096dbc884f39473e92adeb1e35d6fb6d89cbd59/regex-2026.3.32-cp311-cp311-win_arm64.whl", hash = "sha256:ded4fc0edf3de792850cb8b04bbf3c5bd725eeaf9df4c27aad510f6eed9c4e19", size = 270594, upload-time = "2026-03-28T21:46:25.857Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/69492c45b0e61b027109d8433a5c3d4f7a90709184c057c7cfc60acb1bfa/regex-2026.3.32-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ad8d372587e659940568afd009afeb72be939c769c552c9b28773d0337251391", size = 490572, upload-time = "2026-03-28T21:46:28.031Z" }, + { url = "https://files.pythonhosted.org/packages/92/0a/7dcffeebe0fcac45a1f9caf80712002d3cbd66d7d69d719315ee142b280f/regex-2026.3.32-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f5747501b69299c6b0b047853771e4ed390510bada68cb16da9c9c2078343f7", size = 292078, upload-time = "2026-03-28T21:46:29.789Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ec/988486058ef49eb931476419bae00f164c4ceb44787c45dc7a54b7de0ea4/regex-2026.3.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db976be51375bca900e008941639448d148c655c9545071965d0571ecc04f5d0", size = 289786, upload-time = "2026-03-28T21:46:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cf/1955bb5567bc491bd63068e17f75ab0c9ff5e9d08466beec7e347f5e768d/regex-2026.3.32-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66a5083c3ffe5a5a95f8281ea47a88072d4f24001d562d1d9d28d4cdc005fec5", size = 796431, upload-time = "2026-03-28T21:46:33.101Z" }, + { url = "https://files.pythonhosted.org/packages/27/8a/67fcbca511b792107540181ee0690df6de877bfbcb41b7ecae7028025ca5/regex-2026.3.32-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e83ce8008b48762be296f1401f19afd9ea29f3d035d1974e0cecb74e9afbd1df", size = 865785, upload-time = "2026-03-28T21:46:35.053Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/0677bc44f2c28305edcabc11933777b9ad34e9e8ded7ba573d24e4bc3ee7/regex-2026.3.32-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3aa21bad31db904e0b9055e12c8282df62d43169c4a9d2929407060066ebc74", size = 913593, upload-time = "2026-03-28T21:46:36.835Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/661043d1c263b0d9d10c6ff4e9c9745f3df9641c62b51f96a3473638e7ce/regex-2026.3.32-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f54840bea73541652f1170dc63402a5b776fc851ad36a842da9e5163c1f504a0", size = 801512, upload-time = "2026-03-28T21:46:38.587Z" }, + { url = "https://files.pythonhosted.org/packages/ff/27/74c986061380e1811a46cf04cdf9c939db9f8c0e63953eddfe37ffd633ea/regex-2026.3.32-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2ffbadc647325dd4e3118269bda93ded1eb5f5b0c3b7ba79a3da9fbd04f248e9", size = 776182, upload-time = "2026-03-28T21:46:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c8/d833397b70cd1bacfcdc0a611f0e2c1f5b91fee8eedd88affcee770cbbb6/regex-2026.3.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:66d3126afe7eac41759cd5f0b3b246598086e88e70527c0d68c9e615b81771c4", size = 785837, upload-time = "2026-03-28T21:46:42.926Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/fa226b72989b5b93db6926fab5478115e085dfcf077e18d2cb386be0fd23/regex-2026.3.32-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f785f44a44702dea89b28bce5bc82552490694ce4e144e21a4f0545e364d2150", size = 860612, upload-time = "2026-03-28T21:46:44.8Z" }, + { url = "https://files.pythonhosted.org/packages/04/28/bdd2fc0c055a1b15702bd4084829bbb6b06095f27990e5bee52b2898ea03/regex-2026.3.32-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b7836aa13721dbdef658aebd11f60d00de633a95726521860fe1f6be75fa225a", size = 765285, upload-time = "2026-03-28T21:46:46.625Z" }, + { url = "https://files.pythonhosted.org/packages/b4/da/21f5e2a35a191b27e5a47cccb3914c99e139b49b1342d3f36e64e8cc60f7/regex-2026.3.32-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5336b1506142eb0f23c96fb4a34b37c4fefd4fed2a7042069f3c8058efe17855", size = 851963, upload-time = "2026-03-28T21:46:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/f4/04ed04ebf335a44083695c22772be6a42efa31900415555563acf02cb4de/regex-2026.3.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b56993a7aeb4140c4770f4f7965c9e5af4f024457d06e23c01b0d47501cb18ed", size = 788332, upload-time = "2026-03-28T21:46:50.454Z" }, + { url = "https://files.pythonhosted.org/packages/21/25/5355908f479d0dc13d044f88270cdcabc8723efc12e4c2b19e5a94ff1a96/regex-2026.3.32-cp312-cp312-win32.whl", hash = "sha256:d363660f9ef8c734495598d2f3e527fb41f745c73159dc0d743402f049fb6836", size = 266847, upload-time = "2026-03-28T21:46:52.125Z" }, + { url = "https://files.pythonhosted.org/packages/00/e5/3be71c781a031db5df00735b613895ad5fdbf86c6e3bbea5fbbd7bfb5902/regex-2026.3.32-cp312-cp312-win_amd64.whl", hash = "sha256:c9f261ad3cd97257dc1d9355bfbaa7dd703e06574bffa0fa8fe1e31da915ee38", size = 278034, upload-time = "2026-03-28T21:46:54.096Z" }, + { url = "https://files.pythonhosted.org/packages/31/5f/27f1e0b1eea4faa99c66daca34130af20c44fae0237bbc98b87999dbc4a8/regex-2026.3.32-cp312-cp312-win_arm64.whl", hash = "sha256:89e50667e7e8c0e7903e4d644a2764fffe9a3a5d6578f72ab7a7b4205bf204b7", size = 270673, upload-time = "2026-03-28T21:46:56.046Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ba/9c1819f302b42b5fbd4139ead6280e9ec37d19bbe33379df0039b2a57bb4/regex-2026.3.32-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c6d9c6e783b348f719b6118bb3f187b2e138e3112576c9679eb458cc8b2e164b", size = 490394, upload-time = "2026-03-28T21:46:58.112Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0b/f62b0ce79eb83ca82fffea1736289d29bc24400355968301406789bcebd2/regex-2026.3.32-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f21ae18dfd15752cdd98d03cbd7a3640be826bfd58482a93f730dbd24d7b9fb", size = 291993, upload-time = "2026-03-28T21:47:00.198Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d8/ba0f8f81f88cd20c0b27acc123561ac5495ea33f800f0b8ebed2038b23eb/regex-2026.3.32-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:844d88509c968dd44b30daeefac72b038b1bf31ac372d5106358ab01d393c48b", size = 289618, upload-time = "2026-03-28T21:47:02.269Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0d/b47a0e68bc511c195ff129c0311a4cd79b954b8676193a9d03a97c623a91/regex-2026.3.32-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fc918cd003ba0d066bf0003deb05a259baaaab4dc9bd4f1207bbbe64224857a", size = 796427, upload-time = "2026-03-28T21:47:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/32b05aa8fde7789ba316533c0f30e87b6b5d38d6d7f8765eadc5aab84671/regex-2026.3.32-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbc458a292aee57d572075f22c035fa32969cdb7987d454e3e34d45a40a0a8b4", size = 865850, upload-time = "2026-03-28T21:47:05.982Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/828d8095501f237b83f630d4069eea8c0e5cb6a204e859cf0b67c223ce12/regex-2026.3.32-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:987cdfcfb97a249abc3601ad53c7de5c370529f1981e4c8c46793e4a1e1bfe8e", size = 913578, upload-time = "2026-03-28T21:47:08.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f8/acf1eb80f58852e85bd39a6ddfa78ce2243ddc8de8da7582e6ba657da593/regex-2026.3.32-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5d88fa37ba5e8a80ca8d956b9ea03805cfa460223ac94b7d4854ee5e30f3173", size = 801536, upload-time = "2026-03-28T21:47:10.206Z" }, + { url = "https://files.pythonhosted.org/packages/9f/05/986cdf8d12693451f5889aaf4ea4f65b2c49b1152ae814fa1fb75439e40b/regex-2026.3.32-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d082be64e51671dd5ee1c208c92da2ddda0f2f20d8ef387e57634f7e97b6aae", size = 776226, upload-time = "2026-03-28T21:47:12.891Z" }, + { url = "https://files.pythonhosted.org/packages/32/02/945a6a2348ca1c6608cb1747275c8affd2ccd957d4885c25218a86377912/regex-2026.3.32-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1d7fa44aece1fa02b8927441614c96520253a5cad6a96994e3a81e060feed55", size = 785933, upload-time = "2026-03-28T21:47:14.795Z" }, + { url = "https://files.pythonhosted.org/packages/53/12/c5bab6cc679ad79a45427a98c4e70809586ac963c5ad54a9217533c4763e/regex-2026.3.32-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d478a2ca902b6ef28ffc9521e5f0f728d036abe35c0b250ee8ae78cfe7c5e44e", size = 860671, upload-time = "2026-03-28T21:47:16.985Z" }, + { url = "https://files.pythonhosted.org/packages/bf/68/8d85f98c2443469facabef62b82b851d369b13f92bec2ca7a3808deaa47b/regex-2026.3.32-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2820d2231885e97aff0fcf230a19ebd5d2b5b8a1ba338c20deb34f16db1c7897", size = 765335, upload-time = "2026-03-28T21:47:18.872Z" }, + { url = "https://files.pythonhosted.org/packages/89/a7/d8a9c270916107a501fca63b748547c6c77e570d19f16a29b557ce734f3d/regex-2026.3.32-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc8ced733d6cd9af5e412f256a32f7c61cd2d7371280a65c689939ac4572499f", size = 851913, upload-time = "2026-03-28T21:47:20.793Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8e/03d392b26679914ccf21f83d18ad4443232d2f8c3e2c30a962d4e3918d9c/regex-2026.3.32-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:847087abe98b3c1ebf1eb49d6ef320dbba75a83ee4f83c94704580f1df007dd4", size = 788447, upload-time = "2026-03-28T21:47:22.628Z" }, + { url = "https://files.pythonhosted.org/packages/cf/df/692227d23535a50604333068b39eb262626db780ab1e1b19d83fc66853aa/regex-2026.3.32-cp313-cp313-win32.whl", hash = "sha256:d21a07edddb3e0ca12a8b8712abc8452481c3d3db19ae87fc94e9842d005964b", size = 266834, upload-time = "2026-03-28T21:47:24.778Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/13e4e56adc16ba607cffa1fe880f233eb9ded8ab8a8580619683c9e4ce48/regex-2026.3.32-cp313-cp313-win_amd64.whl", hash = "sha256:3c054e39a9f85a3d76c62a1d50c626c5e9306964eaa675c53f61ff7ec1204bbb", size = 277972, upload-time = "2026-03-28T21:47:26.627Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1c/80a86dbb2b416fec003b1801462bdcebbf1d43202ed5acb176e99c1ba369/regex-2026.3.32-cp313-cp313-win_arm64.whl", hash = "sha256:b2e9c2ea2e93223579308263f359eab8837dc340530b860cb59b713651889f14", size = 270649, upload-time = "2026-03-28T21:47:28.551Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/e38372da599dc1c39c599907ec535016d110034bd3701ce36554f59767ef/regex-2026.3.32-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5d86e3fb08c94f084a625c8dc2132a79a3a111c8bf6e2bc59351fa61753c2f6e", size = 494495, upload-time = "2026-03-28T21:47:30.642Z" }, + { url = "https://files.pythonhosted.org/packages/5f/27/6e29ece8c9ce01001ece1137fa21c8707529c2305b22828f63623b0eb262/regex-2026.3.32-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b6f366a5ef66a2df4d9e68035cfe9f0eb8473cdfb922c37fac1d169b468607b0", size = 293988, upload-time = "2026-03-28T21:47:32.553Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/8752e18bb87a2fe728b73b0f83c082eb162a470766063f8028759fb26844/regex-2026.3.32-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b8fca73e16c49dd972ce3a88278dfa5b93bf91ddef332a46e9443abe21ca2f7c", size = 292634, upload-time = "2026-03-28T21:47:34.651Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7b/d7729fe294e23e9c7c3871cb69d49059fa7d65fd11e437a2cbea43f6615d/regex-2026.3.32-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b953d9d496d19786f4d46e6ba4b386c6e493e81e40f9c5392332458183b0599d", size = 810532, upload-time = "2026-03-28T21:47:36.839Z" }, + { url = "https://files.pythonhosted.org/packages/fd/49/4dae7b000659f611b17b9c1541fba800b0569e4060debc4635ef1b23982c/regex-2026.3.32-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b565f25171e04d4fad950d1fa837133e3af6ea6f509d96166eed745eb0cf63bc", size = 871919, upload-time = "2026-03-28T21:47:39.192Z" }, + { url = "https://files.pythonhosted.org/packages/83/85/aa8ad3977b9399861db3df62b33fe5fef6932ee23a1b9f4f357f58f2094b/regex-2026.3.32-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f28eac18a8733a124444643a66ac96fef2c0ad65f50034e0a043b90333dc677f", size = 916550, upload-time = "2026-03-28T21:47:41.618Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c0/6379d7f5b59ff0656ba49cf666d5013ecee55e83245275b310b0ffc79143/regex-2026.3.32-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cdd508664430dd51b8888deb6c5b416d8de046b2e11837254378d31febe4a98", size = 814988, upload-time = "2026-03-28T21:47:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/2c/af/2dfddc64074bd9b70e27e170ee9db900542e2870210b489ad4471416ba86/regex-2026.3.32-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5c35d097f509cf7e40d20d5bee548d35d6049b36eb9965e8d43e4659923405b9", size = 786337, upload-time = "2026-03-28T21:47:46.076Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2f/4eb8abd705236402b4fe0e130971634deffb1855e2028bf02a2b7c0e841c/regex-2026.3.32-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:85c9b0c131427470a6423baa0a9330be6fd8c3630cc3ee6fdee03360724cbec5", size = 800029, upload-time = "2026-03-28T21:47:48.356Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2c/77d9ca2c9df483b51b4b1291c96d79c9ae301077841c4db39bc822f6b4c6/regex-2026.3.32-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e50af656c15e2723eeb7279c0837e07accc594b95ec18b86821a4d44b51b24bf", size = 865843, upload-time = "2026-03-28T21:47:50.762Z" }, + { url = "https://files.pythonhosted.org/packages/48/10/306f477a509f4eed699071b1f031d89edd5a2b5fa28c8ede5b2638eaba82/regex-2026.3.32-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4bc32b4dbdb4f9f300cf9f38f8ea2ce9511a068ffaa45ac1373ee7a943f1d810", size = 772473, upload-time = "2026-03-28T21:47:52.771Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f6/54bd83ec46ac037de2beb049afc9dd5d2769c6ecaadf7856254ce610e62a/regex-2026.3.32-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3e5d1802cba785210a4a800e63fcee7a228649a880f3bf7f2aadccb151a834b", size = 856805, upload-time = "2026-03-28T21:47:55.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/ee0e7d14de1fc6582d5782f072db6c61465a38a4142f88e175dda494b536/regex-2026.3.32-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ef250a3f5e93182193f5c927c5e9575b2cb14b80d03e258bc0b89cc5de076b60", size = 801875, upload-time = "2026-03-28T21:47:57.434Z" }, + { url = "https://files.pythonhosted.org/packages/8a/06/0fa9daca59d07b6aabd8e0468d3b86fd578576a157206fbcddbfc2298f7d/regex-2026.3.32-cp313-cp313t-win32.whl", hash = "sha256:9cf7036dfa2370ccc8651521fcbb40391974841119e9982fa312b552929e6c85", size = 269892, upload-time = "2026-03-28T21:47:59.674Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/77f16b5ad9f10ca574f03d84a354b359b0ac33f85054f2f2daafc9f7b807/regex-2026.3.32-cp313-cp313t-win_amd64.whl", hash = "sha256:c940e00e8d3d10932c929d4b8657c2ea47d2560f31874c3e174c0d3488e8b865", size = 281318, upload-time = "2026-03-28T21:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/c6/47/db4446faaea8d01c8315c9c89c7dc6abbb3305e8e712e9b23936095c4d58/regex-2026.3.32-cp313-cp313t-win_arm64.whl", hash = "sha256:ace48c5e157c1e58b7de633c5e257285ce85e567ac500c833349c363b3df69d4", size = 272366, upload-time = "2026-03-28T21:48:03.748Z" }, + { url = "https://files.pythonhosted.org/packages/32/68/ff024bf6131b7446a791a636dbbb7fa732d586f33b276d84b3460ea49393/regex-2026.3.32-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a416ee898ecbc5d8b283223b4cf4d560f93244f6f7615c1bd67359744b00c166", size = 490430, upload-time = "2026-03-28T21:48:05.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/72/039d9164817ee298f2a2d0246001afe662241dcbec0eedd1fe03e2a2555e/regex-2026.3.32-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d76d62909bfb14521c3f7cfd5b94c0c75ec94b0a11f647d2f604998962ec7b6c", size = 291948, upload-time = "2026-03-28T21:48:07.666Z" }, + { url = "https://files.pythonhosted.org/packages/06/9d/77f684d90ffe3e99b828d3cabb87a0f1601d2b9decd1333ff345809b1d02/regex-2026.3.32-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:631f7d95c83f42bccfe18946a38ad27ff6b6717fb4807e60cf24860b5eb277fc", size = 289786, upload-time = "2026-03-28T21:48:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/83/70/bd76069a0304e924682b2efd8683a01617a7e1da9b651af73039d8da76a4/regex-2026.3.32-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12917c6c6813ffcdfb11680a04e4d63c5532b88cf089f844721c5f41f41a63ad", size = 796672, upload-time = "2026-03-28T21:48:11.568Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/c2d7d9a5671e111a2c16d57e0cb03e1ce35b28a115901590528aa928bb5b/regex-2026.3.32-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e221b615f83b15887636fcb90ed21f1a19541366f8b7ba14ba1ad8304f4ded4", size = 866556, upload-time = "2026-03-28T21:48:14.081Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b9/9921a31931d0bc3416ac30205471e0e2ed60dcbd16fc922bbd69b427322b/regex-2026.3.32-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f9ae4755fa90f1dc2d0d393d572ebc134c0fe30fcfc0ab7e67c1db15f192041", size = 912787, upload-time = "2026-03-28T21:48:16.548Z" }, + { url = "https://files.pythonhosted.org/packages/41/ab/2c1bc8ab99f63cdabdbc7823af8f4cfcd6ddbb2babf01861826c3f1ad44d/regex-2026.3.32-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a094e9dcafedfb9d333db5cf880304946683f43a6582bb86688f123335122929", size = 800879, upload-time = "2026-03-28T21:48:18.971Z" }, + { url = "https://files.pythonhosted.org/packages/49/e5/0be716eb2c0b2ae3a439e44432534e82b2f81848af64cb21c0473ad8ae46/regex-2026.3.32-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c1cecea3e477af105f32ef2119b8d895f297492e41d317e60d474bc4bffd62ff", size = 776332, upload-time = "2026-03-28T21:48:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/26/80/114a61bd25dec7d1070930eaef82aadf9b05961a37629e7cca7bc3fc2257/regex-2026.3.32-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f26262900edd16272b6360014495e8d68379c6c6e95983f9b7b322dc928a1194", size = 786384, upload-time = "2026-03-28T21:48:23.277Z" }, + { url = "https://files.pythonhosted.org/packages/0c/78/be0a6531f8db426e8e60d6356aeef8e9cc3f541655a648c4968b63c87a88/regex-2026.3.32-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb22fa9ee6a0acb22fc9aecce5f9995fe4d2426ed849357d499d62608fbd7f9", size = 861381, upload-time = "2026-03-28T21:48:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/45/b1/e5076fbe45b8fb39672584b1b606d512f5bd3a43155be68a95f6b88c1fc5/regex-2026.3.32-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9b9118a78e031a2e4709cd2fcc3028432e89b718db70073a8da574c249b5b249", size = 765434, upload-time = "2026-03-28T21:48:27.494Z" }, + { url = "https://files.pythonhosted.org/packages/a3/da/fd65d68b897f8b52b1390d20d776fa753582484724a9cb4f4c26de657ae5/regex-2026.3.32-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b193ed199848aa96618cd5959c1582a0bf23cd698b0b900cb0ffe81b02c8659c", size = 851501, upload-time = "2026-03-28T21:48:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d6/1e9c991c32022a9312e9124cc974961b3a2501338de2cd1cce75a3612d7a/regex-2026.3.32-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:10fb2aaae1aaadf7d43c9f3c2450404253697bf8b9ce360bd5418d1d16292298", size = 788076, upload-time = "2026-03-28T21:48:32.025Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5b/b23c72f6d607cbb24ef42acf0c7c2ef4eee1377a9f7ba43b312f889edfbb/regex-2026.3.32-cp314-cp314-win32.whl", hash = "sha256:110ba4920721374d16c4c8ea7ce27b09546d43e16aea1d7f43681b5b8f80ba61", size = 272255, upload-time = "2026-03-28T21:48:34.355Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ec/32bbcc42366097a8cea2c481e02964be6c6fa5ccfb0fa9581686af0bec5f/regex-2026.3.32-cp314-cp314-win_amd64.whl", hash = "sha256:245667ad430745bae6a1e41081872d25819d86fbd9e0eec485ba00d9f78ad43d", size = 281160, upload-time = "2026-03-28T21:48:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e4/89038a028cb68e719fa03ab1ad603649fc199bcda12270d2ac7b471b8f5d/regex-2026.3.32-cp314-cp314-win_arm64.whl", hash = "sha256:1ca02ff0ef33e9d8276a1fcd6d90ff6ea055a32c9149c0050b5b67e26c6d2c51", size = 273688, upload-time = "2026-03-28T21:48:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/30/6e/87caccd608837a1fa4f8c7edc48e206103452b9bbc94fc724fa39340e807/regex-2026.3.32-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:51fb7e26f91f9091fd8ec6a946f99b15d3bc3667cb5ddc73dd6cb2222dd4a1cc", size = 494506, upload-time = "2026-03-28T21:48:41.327Z" }, + { url = "https://files.pythonhosted.org/packages/16/53/a922e6b24694d70bdd68fc3fd076950e15b1b418cff9d2cc362b3968d86f/regex-2026.3.32-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:51a93452034d671b0e21b883d48ea66c5d6a05620ee16a9d3f229e828568f3f0", size = 293986, upload-time = "2026-03-28T21:48:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/60/e4/0cb32203c1aebad0577fcd5b9af1fe764869e617d5234bc6a0ad284299ea/regex-2026.3.32-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:03c2ebd15ff51e7b13bb3dc28dd5ac18cd39e59ebb40430b14ae1a19e833cff1", size = 292677, upload-time = "2026-03-28T21:48:45.772Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f8/5006b70291469d4174dd66ad162802e2f68419c0f2a7952d0c76c1288cfa/regex-2026.3.32-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bf2f3c2c5bd8360d335c7dcd4a9006cf1dabae063ee2558ee1b07bbc8a20d88", size = 810661, upload-time = "2026-03-28T21:48:48.147Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9b/438763a20d22cd1f65f95c8f030dd25df2d80a941068a891d21a5f240456/regex-2026.3.32-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a4a3189a99ecdd1c13f42513ab3fc7fa8311b38ba7596dd98537acb8cd9acc3", size = 872156, upload-time = "2026-03-28T21:48:50.739Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5b/1341287887ac982ed9f5f60125e440513ffe354aa7e3681940495af7c12a/regex-2026.3.32-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c0bbfbd38506e1ea96a85da6782577f06239cb9fcf9696f1ea537c980c0680b", size = 916749, upload-time = "2026-03-28T21:48:53.57Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/1d2b48b8e94debfffc6fefb84d2a86a178cc208652a1d6493d5f29821c70/regex-2026.3.32-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8aaf8ee8f34b677f90742ca089b9c83d64bdc410528767273c816a863ed57327", size = 814788, upload-time = "2026-03-28T21:48:55.905Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d9/7dacb34c43adaeb954518d851f3e5d3ce495ac00a9d6010e3b4b59917c4a/regex-2026.3.32-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ea568832eca219c2be1721afa073c1c9eb8f98a9733fdedd0a9747639fc22a5", size = 786594, upload-time = "2026-03-28T21:48:58.404Z" }, + { url = "https://files.pythonhosted.org/packages/ea/72/28295068c92dbd6d3ce4fd22554345cf504e957cc57dadeda4a64fa86a57/regex-2026.3.32-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e4c8fa46aad1a11ae2f8fcd1c90b9d55e18925829ac0d98c5bb107f93351745", size = 800167, upload-time = "2026-03-28T21:49:01.226Z" }, + { url = "https://files.pythonhosted.org/packages/ca/17/b10745adeca5b8d52da050e7c746137f5d01dabc6dbbe6e8d9d821dc65c1/regex-2026.3.32-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cec365d44835b043d7b3266487797639d07d621bec9dc0ea224b00775797cc1", size = 865906, upload-time = "2026-03-28T21:49:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/45/9d/1acbcce765044ac0c87f453f4876e0897f7a61c10315262f960184310798/regex-2026.3.32-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:09e26cad1544d856da85881ad292797289e4406338afe98163f3db9f7fac816c", size = 772642, upload-time = "2026-03-28T21:49:06.811Z" }, + { url = "https://files.pythonhosted.org/packages/24/41/1ef8b4811355ad7b9d7579d3aeca00f18b7bc043ace26c8c609b9287346d/regex-2026.3.32-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:6062c4ef581a3e9e503dccf4e1b7f2d33fdc1c13ad510b287741ac73bc4c6b27", size = 856927, upload-time = "2026-03-28T21:49:09.373Z" }, + { url = "https://files.pythonhosted.org/packages/97/b1/0dc1d361be80ec1b8b707ada041090181133a7a29d438e432260a4b26f9a/regex-2026.3.32-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88ebc0783907468f17fca3d7821b30f9c21865a721144eb498cb0ff99a67bcac", size = 801910, upload-time = "2026-03-28T21:49:11.818Z" }, + { url = "https://files.pythonhosted.org/packages/b5/db/1a23f767fa250844772a9464306d34e0fafe2c317303b88a1415096b6324/regex-2026.3.32-cp314-cp314t-win32.whl", hash = "sha256:e480d3dac06c89bc2e0fd87524cc38c546ac8b4a38177650745e64acbbcfdeba", size = 275714, upload-time = "2026-03-28T21:49:14.528Z" }, + { url = "https://files.pythonhosted.org/packages/c2/2b/616d31b125ca76079d74d6b1d84ec0860ffdb41c379151135d06e35a8633/regex-2026.3.32-cp314-cp314t-win_amd64.whl", hash = "sha256:67015a8162d413af9e3309d9a24e385816666fbf09e48e3ec43342c8536f7df6", size = 285722, upload-time = "2026-03-28T21:49:16.642Z" }, + { url = "https://files.pythonhosted.org/packages/7e/91/043d9a00d6123c5fa22a3dc96b10445ce434a8110e1d5e53efb01f243c8b/regex-2026.3.32-cp314-cp314t-win_arm64.whl", hash = "sha256:1a6ac1ed758902e664e0d95c1ee5991aa6fb355423f378ed184c6ec47a1ec0e9", size = 275700, upload-time = "2026-03-28T21:49:19.348Z" }, +] + +[[package]] +name = "requests" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/b3/2cb7c17b6c4cf8ca983204255d3f1d95eda7213e247e6947a0ee2c747a2c/tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970", size = 1051991, upload-time = "2025-10-06T20:21:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/27/0f/df139f1df5f6167194ee5ab24634582ba9a1b62c6b996472b0277ec80f66/tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16", size = 995798, upload-time = "2025-10-06T20:21:35.579Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5d/26a691f28ab220d5edc09b9b787399b130f24327ef824de15e5d85ef21aa/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030", size = 1129865, upload-time = "2025-10-06T20:21:36.675Z" }, + { url = "https://files.pythonhosted.org/packages/b2/94/443fab3d4e5ebecac895712abd3849b8da93b7b7dec61c7db5c9c7ebe40c/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134", size = 1152856, upload-time = "2025-10-06T20:21:37.873Z" }, + { url = "https://files.pythonhosted.org/packages/54/35/388f941251b2521c70dd4c5958e598ea6d2c88e28445d2fb8189eecc1dfc/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a", size = 1195308, upload-time = "2025-10-06T20:21:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/f8/00/c6681c7f833dd410576183715a530437a9873fa910265817081f65f9105f/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892", size = 1255697, upload-time = "2025-10-06T20:21:41.154Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d2/82e795a6a9bafa034bf26a58e68fe9a89eeaaa610d51dbeb22106ba04f0a/tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1", size = 879375, upload-time = "2025-10-06T20:21:43.201Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565, upload-time = "2025-10-06T20:21:44.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284, upload-time = "2025-10-06T20:21:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201, upload-time = "2025-10-06T20:21:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444, upload-time = "2025-10-06T20:21:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080, upload-time = "2025-10-06T20:21:49.246Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240, upload-time = "2025-10-06T20:21:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422, upload-time = "2025-10-06T20:21:51.734Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, + { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, + { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, + { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, + { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/6c64bdbf71f58ccde7919e00491812556f446a5291573af92c49a5e9aaef/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b197cd5424cf89fb019ca7f53641d05bfe34b1879614bed111c9c313b5574cd8", size = 591617, upload-time = "2026-02-20T22:50:24.532Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f0/758c3b0fb0c4871c7704fef26a5bc861de4f8a68e4831669883bebe07b0f/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:12c65020ba6cb6abe1d57fcbfc2d0ea0506c67049ee031714057f5caf0f9bc9c", size = 303702, upload-time = "2026-02-20T22:50:40.687Z" }, + { url = "https://files.pythonhosted.org/packages/85/89/d91862b544c695cd58855efe3201f83894ed82fffe34500774238ab8eba7/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b5d2ad28063d422ccc2c28d46471d47b61a58de885d35113a8f18cb547e25bf", size = 337678, upload-time = "2026-02-20T22:50:39.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6b/cf342ba8a898f1de024be0243fac67c025cad530c79ea7f89c4ce718891a/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da2234387b45fde40b0fedfee64a0ba591caeea9c48c7698ab6e2d85c7991533", size = 343711, upload-time = "2026-02-20T22:50:43.965Z" }, + { url = "https://files.pythonhosted.org/packages/b3/20/049418d094d396dfa6606b30af925cc68a6670c3b9103b23e6990f84b589/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50fffc2827348c1e48972eed3d1c698959e63f9d030aa5dd82ba451113158a62", size = 476731, upload-time = "2026-02-20T22:50:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/77/a1/0857f64d53a90321e6a46a3d4cc394f50e1366132dcd2ae147f9326ca98b/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dbe718765f70f5b7f9b7f66b6a937802941b1cc56bcf642ce0274169741e01", size = 338902, upload-time = "2026-02-20T22:50:33.927Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d0/5bf7cbf1ac138c92b9ac21066d18faf4d7e7f651047b700eb192ca4b9fdb/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:258186964039a8e36db10810c1ece879d229b01331e09e9030bc5dcabe231bd2", size = 364700, upload-time = "2026-02-20T22:50:21.732Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/43/76ded108b296a49f52de6bac5192ca1c4be84e886f9b5c9ba8427d9694fd/werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351", size = 875700, upload-time = "2026-03-24T01:08:07.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, + { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, + { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, + { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" }, + { url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" }, + { url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" }, + { url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" }, + { url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" }, + { url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" }, + { url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" }, + { url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" }, + { url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" }, + { url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/28efd1d371f1acd037ac64ed1c5e2b41514a6cc937dd6ab6a13ab9f0702f/zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd", size = 795256, upload-time = "2025-09-14T22:15:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/ef34ef77f1ee38fc8e4f9775217a613b452916e633c4f1d98f31db52c4a5/zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7", size = 640565, upload-time = "2025-09-14T22:15:58.177Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1b/4fdb2c12eb58f31f28c4d28e8dc36611dd7205df8452e63f52fb6261d13e/zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550", size = 5345306, upload-time = "2025-09-14T22:16:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/73/28/a44bdece01bca027b079f0e00be3b6bd89a4df180071da59a3dd7381665b/zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d", size = 5055561, upload-time = "2025-09-14T22:16:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/e9/74/68341185a4f32b274e0fc3410d5ad0750497e1acc20bd0f5b5f64ce17785/zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b", size = 5402214, upload-time = "2025-09-14T22:16:04.109Z" }, + { url = "https://files.pythonhosted.org/packages/8b/67/f92e64e748fd6aaffe01e2b75a083c0c4fd27abe1c8747fee4555fcee7dd/zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0", size = 5449703, upload-time = "2025-09-14T22:16:06.312Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e5/6d36f92a197c3c17729a2125e29c169f460538a7d939a27eaaa6dcfcba8e/zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0", size = 5556583, upload-time = "2025-09-14T22:16:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/41939e60d8d7ebfe2b747be022d0806953799140a702b90ffe214d557638/zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd", size = 5045332, upload-time = "2025-09-14T22:16:10.444Z" }, + { url = "https://files.pythonhosted.org/packages/b3/87/d3ee185e3d1aa0133399893697ae91f221fda79deb61adbe998a7235c43f/zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701", size = 5572283, upload-time = "2025-09-14T22:16:12.128Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/58635ae6104df96671076ac7d4ae7816838ce7debd94aecf83e30b7121b0/zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1", size = 4959754, upload-time = "2025-09-14T22:16:14.225Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/57e9cb0a9983e9a229dd8fd2e6e96593ef2aa82a3907188436f22b111ccd/zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150", size = 5266477, upload-time = "2025-09-14T22:16:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/ee891e5edf33a6ebce0a028726f0bbd8567effe20fe3d5808c42323e8542/zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab", size = 5440914, upload-time = "2025-09-14T22:16:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/a8522c28c08031a9521f27abc6f78dbdee7312a7463dd2cfc658b813323b/zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e", size = 5819847, upload-time = "2025-09-14T22:16:20.559Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/4c91411805c3f7b6f31c60e78ce347ca48f6f16d552fc659af6ec3b73202/zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74", size = 5363131, upload-time = "2025-09-14T22:16:22.206Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d6/8c4bd38a3b24c4c7676a7a3d8de85d6ee7a983602a734b9f9cdefb04a5d6/zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa", size = 436469, upload-time = "2025-09-14T22:16:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/93/90/96d50ad417a8ace5f841b3228e93d1bb13e6ad356737f42e2dde30d8bd68/zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e", size = 506100, upload-time = "2025-09-14T22:16:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +] From 6df02d2b73ad065cc46cf0682316782aa377cc0a Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 30 Mar 2026 16:58:59 +0000 Subject: [PATCH 231/296] chore: release v4.0.4 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1467ef436..5a108f28c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.0.3" +version = "4.0.4" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 3cc22adf7..7a5a7d699 100644 --- a/uv.lock +++ b/uv.lock @@ -550,7 +550,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.0.3" +version = "4.0.4" source = { editable = "." } dependencies = [ { name = "backoff" }, From 6bae66109df61a843571f22c136d6f1fb80ad48c Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Tue, 31 Mar 2026 10:01:07 +0200 Subject: [PATCH 232/296] build: set exclude-newer to 7 days (#1595) --- pyproject.toml | 4 + uv.lock | 434 +++++++++++++++++++++++++------------------------ 2 files changed, 223 insertions(+), 215 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5a108f28c..3240a1764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,10 @@ docs = [ requires = ["uv_build>=0.11.2,<0.12.0"] build-backend = "uv_build" +[tool.uv] +# Basic protection against supply chain attacks +exclude-newer = "7 days" + [tool.uv.build-backend] module-root = "" diff --git a/uv.lock b/uv.lock index 7a5a7d699..938561497 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,10 @@ version = 1 revision = 3 requires-python = ">=3.10, <4.0" +[options] +exclude-newer = "2026-03-23T20:02:18.050568Z" +exclude-newer-span = "P7D" + [[package]] name = "annotated-types" version = "0.7.0" @@ -13,16 +17,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.13.0" +version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] [[package]] @@ -258,14 +262,14 @@ wheels = [ [[package]] name = "googleapis-common-protos" -version = "1.73.1" +version = "1.73.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506, upload-time = "2026-03-26T22:17:38.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/96/a0205167fa0154f4a542fd6925bdc63d039d88dab3588b875078107e6f06/googleapis_common_protos-1.73.0.tar.gz", hash = "sha256:778d07cd4fbeff84c6f7c72102f0daf98fa2bfd3fa8bea426edc545588da0b5a", size = 147323, upload-time = "2026-03-06T21:53:09.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556, upload-time = "2026-03-26T22:15:58.455Z" }, + { url = "https://files.pythonhosted.org/packages/69/28/23eea8acd65972bbfe295ce3666b28ac510dfcb115fac089d3edb0feb00a/googleapis_common_protos-1.73.0-py3-none-any.whl", hash = "sha256:dfdaaa2e860f242046be561e6d6cb5c5f1541ae02cfbcb034371aadb2942b4e8", size = 297578, upload-time = "2026-03-06T21:52:33.933Z" }, ] [[package]] @@ -467,11 +471,11 @@ wheels = [ [[package]] name = "jsonpointer" -version = "3.1.1" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/bf/9ecc036fbc15cf4153ea6ed4dbeed31ef043f762cccc9d44a534be8319b0/jsonpointer-3.1.0.tar.gz", hash = "sha256:f9b39abd59ba8c1de8a4ff16141605d2a8dacc4dd6cf399672cf237bfe47c211", size = 9000, upload-time = "2026-03-20T21:47:09.982Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/cebb241a435cbf4626b5ea096d8385c04416d7ca3082a15299b746e248fa/jsonpointer-3.1.0-py3-none-any.whl", hash = "sha256:f82aa0f745001f169b96473348370b43c3f581446889c41c807bab1db11c8e7b", size = 7651, upload-time = "2026-03-20T21:47:08.792Z" }, ] [[package]] @@ -517,7 +521,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.23" +version = "1.2.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -529,9 +533,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/47/a5f21b651e9cbd7a26c3e5809336d10a0be94ef7bdf6bea47f2ad9fff1a8/langchain_core-1.2.23.tar.gz", hash = "sha256:fdec64f90cfea25317e88d9803c44684af1f4e30dec4e58320dd7393bb0f0785", size = 841684, upload-time = "2026-03-27T23:28:14.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/e4/135ef5bbb5b97bdf15f777b86d7fba2ef8a162723ae96b3c7c1add9891a9/langchain_core-1.2.21.tar.gz", hash = "sha256:1a121d13976dc0908d5a8222262810ea483a4cf2b05006bdba75df5b11b554b3", size = 841063, upload-time = "2026-03-23T18:01:01.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/5a/6ff2d76618e4cac531ea51d4ef44c6add36575a84c3f0f8877aee68c951a/langchain_core-1.2.23-py3-none-any.whl", hash = "sha256:70866dfc5275b7840ce272ff70f0ff216c8666ab25dc1b41964a4ef58c02a3ff", size = 506709, upload-time = "2026-03-27T23:28:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ae/f7591e57d4c6b64b521f9832fc471070902718c59bf135401f3b90a3f7ef/langchain_core-1.2.21-py3-none-any.whl", hash = "sha256:486cb405e2ecb0c407cb5fb5379ed0f919eb4b8a868b60cc8c3c15a3dfb560a7", size = 505756, upload-time = "2026-03-23T18:01:00.176Z" }, ] [[package]] @@ -927,7 +931,7 @@ wheels = [ [[package]] name = "openai" -version = "2.30.0" +version = "2.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -939,9 +943,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/15/52580c8fbc16d0675d516e8749806eda679b16de1e4434ea06fb6feaa610/openai-2.30.0.tar.gz", hash = "sha256:92f7661c990bda4b22a941806c83eabe4896c3094465030dd882a71abe80c885", size = 676084, upload-time = "2026-03-25T22:08:59.96Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/15/203d537e58986b5673e7f232453a2a2f110f22757b15921cbdeea392e520/openai-2.29.0.tar.gz", hash = "sha256:32d09eb2f661b38d3edd7d7e1a2943d1633f572596febe64c0cd370c86d52bec", size = 671128, upload-time = "2026-03-17T17:53:49.599Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/9e/5bfa2270f902d5b92ab7d41ce0475b8630572e71e349b2a4996d14bdda93/openai-2.30.0-py3-none-any.whl", hash = "sha256:9a5ae616888eb2748ec5e0c5b955a51592e0b201a11f4262db920f2a78c5231d", size = 1146656, upload-time = "2026-03-25T22:08:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b1/35b6f9c8cf9318e3dbb7146cc82dab4cf61182a8d5406fc9b50864362895/openai-2.29.0-py3-none-any.whl", hash = "sha256:b7c5de513c3286d17c5e29b92c4c98ceaf0d775244ac8159aeb1bddf840eb42a", size = 1141533, upload-time = "2026-03-17T17:53:47.348Z" }, ] [[package]] @@ -1470,11 +1474,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.20.0" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -1547,15 +1551,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.1" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, ] [[package]] @@ -1638,128 +1642,128 @@ wheels = [ [[package]] name = "regex" -version = "2026.3.32" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/93/5ab3e899c47fa7994e524447135a71cd121685a35c8fe35029005f8b236f/regex-2026.3.32.tar.gz", hash = "sha256:f1574566457161678297a116fa5d1556c5a4159d64c5ff7c760e7c564bf66f16", size = 415605, upload-time = "2026-03-28T21:49:22.012Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/87/ae29a505fdfcec85978f35d30e6de7c0ae37eaf7c287f6e88abd04be27b3/regex-2026.3.32-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:462a041d2160090553572f6bb0be417ab9bb912a08de54cb692829c871ee88c1", size = 489575, upload-time = "2026-03-28T21:45:27.167Z" }, - { url = "https://files.pythonhosted.org/packages/f9/fd/7a56c6a86213e321a309161673667091991630287d7490c5e9ec3db29607/regex-2026.3.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c6f6b027d10f84bfe65049028892b5740878edd9eae5fea0d1710b09b1d257", size = 291288, upload-time = "2026-03-28T21:45:30.886Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/ac2b481011b23f79994d4d80df03d9feccb64fbfc7bbe8dad2c3e8efc50c/regex-2026.3.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:879ae91f2928a13f01a55cfa168acedd2b02b11b4cd8b5bb9223e8cde777ca52", size = 289336, upload-time = "2026-03-28T21:45:32.631Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a2/cf7dfef7a4182e84acbe8919ce7ff50e3545007c2743219e92271b2fbc1c/regex-2026.3.32-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:887a9fa74418d74d645281ee0edcf60694053bd1bc2ebc49eb5e66bfffc6d107", size = 786358, upload-time = "2026-03-28T21:45:34.025Z" }, - { url = "https://files.pythonhosted.org/packages/fb/cb/42bfeb4597206e3171e70c973ca1d39190b48f6cda7546c25f9cb283285f/regex-2026.3.32-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d571f0b2eec3513734ea31a16ce0f7840c0b85a98e7edfa0e328ed144f9ef78f", size = 854179, upload-time = "2026-03-28T21:45:35.713Z" }, - { url = "https://files.pythonhosted.org/packages/90/d8/9f4a7d7edffe7117de23b94696c52065b68e70267d71576d74429d598d9b/regex-2026.3.32-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ada7bd5bb6511d12177a7b00416ce55caee49fbf8c268f26b909497b534cacb", size = 898810, upload-time = "2026-03-28T21:45:37.435Z" }, - { url = "https://files.pythonhosted.org/packages/05/e6/80335c06ddf7fd7a28b97402ebe1ea4fe80a3aa162fba0f7364175f625d1/regex-2026.3.32-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:918db4e34a7ef3d0beee913fa54b34231cc3424676f1c19bdb85f01828d3cd37", size = 790605, upload-time = "2026-03-28T21:45:39.207Z" }, - { url = "https://files.pythonhosted.org/packages/38/0e/91436a89c1636090903d753d90b076784b11b8c67b79b3bde9851a45c4d7/regex-2026.3.32-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:69a847a6ffaa86e8af7b9e7037606e05a6f663deec516ad851e8e05d9908d16a", size = 786550, upload-time = "2026-03-28T21:45:40.993Z" }, - { url = "https://files.pythonhosted.org/packages/2b/fc/ea7364b5e9abd220cebf547f2f8a42044878e9d8b02b3a652f8b807c0cbc/regex-2026.3.32-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2c8d402ea3dfe674288fe3962016affd33b5b27213d2b5db1823ffa4de524c57", size = 770223, upload-time = "2026-03-28T21:45:42.802Z" }, - { url = "https://files.pythonhosted.org/packages/3b/86/aff4ad741e914cc493e7500431cdf14e51bc808b14f1f205469d353a970b/regex-2026.3.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d6b39a2cc5625bbc4fda18919a891eab9aab934eecf83660a90ce20c53621a9a", size = 774436, upload-time = "2026-03-28T21:45:44.212Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e7/060779f504c92320f75b90caab4e57324816020986c27f57414b0a1ebcc9/regex-2026.3.32-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f7cc00089b4c21847852c0ad76fb3680f9833b855a0d30bcec94211c435bff6b", size = 849400, upload-time = "2026-03-28T21:45:46.2Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8e/6544b27f70bfd14e9c50ff5527027acc9b8f9830d352a746f843da7b0627/regex-2026.3.32-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd03e38068faeef937cc6761a250a4aaa015564bd0d61481fefcf15586d31825", size = 757934, upload-time = "2026-03-28T21:45:47.962Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6f/abf2234b3f51da1e693f13bb85e7dbb3bbdd07c04e12e0e105b9bc6006a6/regex-2026.3.32-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e006ea703d5c0f3d112b51ba18af73b58209b954acfe3d8da42eacc9a00e4be6", size = 838479, upload-time = "2026-03-28T21:45:49.845Z" }, - { url = "https://files.pythonhosted.org/packages/db/3c/653f43c3a3643fd221bfaf61ed4a4c8f0ccc25e31a8faa8f1558a892c22c/regex-2026.3.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6980ceb5c1049d4878632f08ba0bf7234c30e741b0dc9081da0f86eca13189d3", size = 778478, upload-time = "2026-03-28T21:45:51.574Z" }, - { url = "https://files.pythonhosted.org/packages/88/dd/5e6bd702d7efc3f2a29bf65dfa46f5159653b3c6f846ddf693e1a7f9a739/regex-2026.3.32-cp310-cp310-win32.whl", hash = "sha256:6128dd0793a87287ea1d8bf16b4250dd96316c464ee15953d5b98875a284d41e", size = 266343, upload-time = "2026-03-28T21:45:53.548Z" }, - { url = "https://files.pythonhosted.org/packages/c4/89/39d04329e858956d2db1d08a10f02be8f6837c964663513ac4393158bef9/regex-2026.3.32-cp310-cp310-win_amd64.whl", hash = "sha256:5aa78c857c1731bdd9863923ffadc816d823edf475c7db6d230c28b53b7bdb5e", size = 278632, upload-time = "2026-03-28T21:45:55.604Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/c7e9ff3c2648408f4cda7224e195ad7a0d68724225d8d9a55eca9055504f/regex-2026.3.32-cp310-cp310-win_arm64.whl", hash = "sha256:34c905a721ddee0f84c99e3e3b59dd4a5564a6fe338222bc89dd4d4df166115c", size = 270593, upload-time = "2026-03-28T21:45:56.994Z" }, - { url = "https://files.pythonhosted.org/packages/92/c1/c68163a6ce455996db71e249a65234b1c9f79a914ea2108c6c9af9e1812a/regex-2026.3.32-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d7855f5e59fcf91d0c9f4a51dc5d8847813832a2230c3e8e35912ccf20baaa2", size = 489568, upload-time = "2026-03-28T21:45:58.791Z" }, - { url = "https://files.pythonhosted.org/packages/96/9c/0bdd47733b832b5caa11e63df14dccdb311b41ab33c1221e249af4421f8f/regex-2026.3.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:18eb45f711e942c27dbed4109830bd070d8d618e008d0db39705f3f57070a4c6", size = 291287, upload-time = "2026-03-28T21:46:00.46Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ff/1977a595f15f8dc355f9cebd875dab67f3faeca1f36b905fe53305bbcaed/regex-2026.3.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed3b8281c5d0944d939c82db4ec2300409dd69ee087f7a75a94f2e301e855fb4", size = 289325, upload-time = "2026-03-28T21:46:02.285Z" }, - { url = "https://files.pythonhosted.org/packages/0a/68/dfa21aef5af4a144702befeb5ff20ea9f9fbe40a4dfd08d56148b5b48b0a/regex-2026.3.32-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad5c53f2e8fcae9144009435ebe3d9832003508cf8935c04542a1b3b8deefa15", size = 790898, upload-time = "2026-03-28T21:46:04.079Z" }, - { url = "https://files.pythonhosted.org/packages/36/26/9424e43e0e31ac3ce1ba0e7232ee91e113a04a579c53331bc0f16a4a5bf7/regex-2026.3.32-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70c634e39c5cda0da05c93d6747fdc957599f7743543662b6dbabdd8d3ba8a96", size = 862462, upload-time = "2026-03-28T21:46:05.923Z" }, - { url = "https://files.pythonhosted.org/packages/63/a8/06573154ac891c6b55b74a88e0fb7c10081c20916b82dd0abc8cef938e13/regex-2026.3.32-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e0f6648fd48f4c73d801c55ab976cd602e2da87de99c07bff005b131f269c6a", size = 906522, upload-time = "2026-03-28T21:46:07.988Z" }, - { url = "https://files.pythonhosted.org/packages/e7/26/46673bb18448c51222c6272c850484a0092f364fae8d0315be9aa1e4baa7/regex-2026.3.32-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5e0fdb5744caf1036dec5510f543164f2144cb64932251f6dfd42fa872b7f9c", size = 798289, upload-time = "2026-03-28T21:46:09.959Z" }, - { url = "https://files.pythonhosted.org/packages/4d/cb/804f1bd5ff08687258e6a92b040aba9b770e626b8d3ba21fffdfa21db2db/regex-2026.3.32-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dab4178a0bc1ef13178832b12db7bc7f562e8f028b2b5be186e370090dc50652", size = 774823, upload-time = "2026-03-28T21:46:12.049Z" }, - { url = "https://files.pythonhosted.org/packages/e5/94/28a58258f8d822fb949c8ff87fc7e5f2a346922360ec084c193b3c95e51c/regex-2026.3.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f95bd07f301135771559101c060f558e2cf896c7df00bec050ca7f93bf11585a", size = 781381, upload-time = "2026-03-28T21:46:13.746Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f3/71e69dbe0543586a3e3532cf36e8c9b38d6d93033161a9799c1e9090eb78/regex-2026.3.32-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2dcca2bceb823c9cc610e57b86a265d7ffc30e9fe98548c609eba8bd3c0c2488", size = 855968, upload-time = "2026-03-28T21:46:15.762Z" }, - { url = "https://files.pythonhosted.org/packages/6d/99/850feec404a02b62e048718ec1b4b98b5c3848cd9ca2316d0bdb65a53f6a/regex-2026.3.32-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:567b57eb987547a23306444e4f6f85d4314f83e65c71d320d898aa7550550443", size = 762785, upload-time = "2026-03-28T21:46:17.394Z" }, - { url = "https://files.pythonhosted.org/packages/40/04/808ab0462a2d19b295a3b42134f5183692f798addfe6a8b6aa5f7c7a35b2/regex-2026.3.32-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b6acb765e7c1f2fa08ac9057a33595e26104d7d67046becae184a8f100932dd9", size = 845797, upload-time = "2026-03-28T21:46:19.269Z" }, - { url = "https://files.pythonhosted.org/packages/06/53/8afcf0fd4bd55440b48442c86cddfe61b0d21c92d96e384c0c47d769f4c3/regex-2026.3.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1ed17104d1be7f807fdec35ec99777168dd793a09510d753f8710590ba54cdd", size = 785200, upload-time = "2026-03-28T21:46:20.939Z" }, - { url = "https://files.pythonhosted.org/packages/99/4d/23d992ab4115456fec520d6c3aae39e0e33739b244ddb39aa4102a0f7ef0/regex-2026.3.32-cp311-cp311-win32.whl", hash = "sha256:c60f1de066eb5a0fd8ee5974de4194bb1c2e7692941458807162ffbc39887303", size = 266351, upload-time = "2026-03-28T21:46:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/62/74/27c3cdb3a3fbbf67f7231b872877416ec817ae84271573d2fd14bf8723d3/regex-2026.3.32-cp311-cp311-win_amd64.whl", hash = "sha256:8fe14e24124ef41220e5992a0f09432f890037df6f93fd3d6b7a0feff2db16b2", size = 278639, upload-time = "2026-03-28T21:46:24.016Z" }, - { url = "https://files.pythonhosted.org/packages/0a/12/6a67bd509f38aec021d63096dbc884f39473e92adeb1e35d6fb6d89cbd59/regex-2026.3.32-cp311-cp311-win_arm64.whl", hash = "sha256:ded4fc0edf3de792850cb8b04bbf3c5bd725eeaf9df4c27aad510f6eed9c4e19", size = 270594, upload-time = "2026-03-28T21:46:25.857Z" }, - { url = "https://files.pythonhosted.org/packages/38/94/69492c45b0e61b027109d8433a5c3d4f7a90709184c057c7cfc60acb1bfa/regex-2026.3.32-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ad8d372587e659940568afd009afeb72be939c769c552c9b28773d0337251391", size = 490572, upload-time = "2026-03-28T21:46:28.031Z" }, - { url = "https://files.pythonhosted.org/packages/92/0a/7dcffeebe0fcac45a1f9caf80712002d3cbd66d7d69d719315ee142b280f/regex-2026.3.32-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f5747501b69299c6b0b047853771e4ed390510bada68cb16da9c9c2078343f7", size = 292078, upload-time = "2026-03-28T21:46:29.789Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ec/988486058ef49eb931476419bae00f164c4ceb44787c45dc7a54b7de0ea4/regex-2026.3.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db976be51375bca900e008941639448d148c655c9545071965d0571ecc04f5d0", size = 289786, upload-time = "2026-03-28T21:46:31.415Z" }, - { url = "https://files.pythonhosted.org/packages/4a/cf/1955bb5567bc491bd63068e17f75ab0c9ff5e9d08466beec7e347f5e768d/regex-2026.3.32-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66a5083c3ffe5a5a95f8281ea47a88072d4f24001d562d1d9d28d4cdc005fec5", size = 796431, upload-time = "2026-03-28T21:46:33.101Z" }, - { url = "https://files.pythonhosted.org/packages/27/8a/67fcbca511b792107540181ee0690df6de877bfbcb41b7ecae7028025ca5/regex-2026.3.32-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e83ce8008b48762be296f1401f19afd9ea29f3d035d1974e0cecb74e9afbd1df", size = 865785, upload-time = "2026-03-28T21:46:35.053Z" }, - { url = "https://files.pythonhosted.org/packages/c2/59/0677bc44f2c28305edcabc11933777b9ad34e9e8ded7ba573d24e4bc3ee7/regex-2026.3.32-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3aa21bad31db904e0b9055e12c8282df62d43169c4a9d2929407060066ebc74", size = 913593, upload-time = "2026-03-28T21:46:36.835Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/661043d1c263b0d9d10c6ff4e9c9745f3df9641c62b51f96a3473638e7ce/regex-2026.3.32-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f54840bea73541652f1170dc63402a5b776fc851ad36a842da9e5163c1f504a0", size = 801512, upload-time = "2026-03-28T21:46:38.587Z" }, - { url = "https://files.pythonhosted.org/packages/ff/27/74c986061380e1811a46cf04cdf9c939db9f8c0e63953eddfe37ffd633ea/regex-2026.3.32-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2ffbadc647325dd4e3118269bda93ded1eb5f5b0c3b7ba79a3da9fbd04f248e9", size = 776182, upload-time = "2026-03-28T21:46:40.69Z" }, - { url = "https://files.pythonhosted.org/packages/b6/c8/d833397b70cd1bacfcdc0a611f0e2c1f5b91fee8eedd88affcee770cbbb6/regex-2026.3.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:66d3126afe7eac41759cd5f0b3b246598086e88e70527c0d68c9e615b81771c4", size = 785837, upload-time = "2026-03-28T21:46:42.926Z" }, - { url = "https://files.pythonhosted.org/packages/e0/53/fa226b72989b5b93db6926fab5478115e085dfcf077e18d2cb386be0fd23/regex-2026.3.32-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f785f44a44702dea89b28bce5bc82552490694ce4e144e21a4f0545e364d2150", size = 860612, upload-time = "2026-03-28T21:46:44.8Z" }, - { url = "https://files.pythonhosted.org/packages/04/28/bdd2fc0c055a1b15702bd4084829bbb6b06095f27990e5bee52b2898ea03/regex-2026.3.32-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b7836aa13721dbdef658aebd11f60d00de633a95726521860fe1f6be75fa225a", size = 765285, upload-time = "2026-03-28T21:46:46.625Z" }, - { url = "https://files.pythonhosted.org/packages/b4/da/21f5e2a35a191b27e5a47cccb3914c99e139b49b1342d3f36e64e8cc60f7/regex-2026.3.32-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5336b1506142eb0f23c96fb4a34b37c4fefd4fed2a7042069f3c8058efe17855", size = 851963, upload-time = "2026-03-28T21:46:48.341Z" }, - { url = "https://files.pythonhosted.org/packages/18/f4/04ed04ebf335a44083695c22772be6a42efa31900415555563acf02cb4de/regex-2026.3.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b56993a7aeb4140c4770f4f7965c9e5af4f024457d06e23c01b0d47501cb18ed", size = 788332, upload-time = "2026-03-28T21:46:50.454Z" }, - { url = "https://files.pythonhosted.org/packages/21/25/5355908f479d0dc13d044f88270cdcabc8723efc12e4c2b19e5a94ff1a96/regex-2026.3.32-cp312-cp312-win32.whl", hash = "sha256:d363660f9ef8c734495598d2f3e527fb41f745c73159dc0d743402f049fb6836", size = 266847, upload-time = "2026-03-28T21:46:52.125Z" }, - { url = "https://files.pythonhosted.org/packages/00/e5/3be71c781a031db5df00735b613895ad5fdbf86c6e3bbea5fbbd7bfb5902/regex-2026.3.32-cp312-cp312-win_amd64.whl", hash = "sha256:c9f261ad3cd97257dc1d9355bfbaa7dd703e06574bffa0fa8fe1e31da915ee38", size = 278034, upload-time = "2026-03-28T21:46:54.096Z" }, - { url = "https://files.pythonhosted.org/packages/31/5f/27f1e0b1eea4faa99c66daca34130af20c44fae0237bbc98b87999dbc4a8/regex-2026.3.32-cp312-cp312-win_arm64.whl", hash = "sha256:89e50667e7e8c0e7903e4d644a2764fffe9a3a5d6578f72ab7a7b4205bf204b7", size = 270673, upload-time = "2026-03-28T21:46:56.046Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ba/9c1819f302b42b5fbd4139ead6280e9ec37d19bbe33379df0039b2a57bb4/regex-2026.3.32-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c6d9c6e783b348f719b6118bb3f187b2e138e3112576c9679eb458cc8b2e164b", size = 490394, upload-time = "2026-03-28T21:46:58.112Z" }, - { url = "https://files.pythonhosted.org/packages/5b/0b/f62b0ce79eb83ca82fffea1736289d29bc24400355968301406789bcebd2/regex-2026.3.32-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f21ae18dfd15752cdd98d03cbd7a3640be826bfd58482a93f730dbd24d7b9fb", size = 291993, upload-time = "2026-03-28T21:47:00.198Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d8/ba0f8f81f88cd20c0b27acc123561ac5495ea33f800f0b8ebed2038b23eb/regex-2026.3.32-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:844d88509c968dd44b30daeefac72b038b1bf31ac372d5106358ab01d393c48b", size = 289618, upload-time = "2026-03-28T21:47:02.269Z" }, - { url = "https://files.pythonhosted.org/packages/fd/0d/b47a0e68bc511c195ff129c0311a4cd79b954b8676193a9d03a97c623a91/regex-2026.3.32-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fc918cd003ba0d066bf0003deb05a259baaaab4dc9bd4f1207bbbe64224857a", size = 796427, upload-time = "2026-03-28T21:47:04.096Z" }, - { url = "https://files.pythonhosted.org/packages/51/d7/32b05aa8fde7789ba316533c0f30e87b6b5d38d6d7f8765eadc5aab84671/regex-2026.3.32-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbc458a292aee57d572075f22c035fa32969cdb7987d454e3e34d45a40a0a8b4", size = 865850, upload-time = "2026-03-28T21:47:05.982Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/828d8095501f237b83f630d4069eea8c0e5cb6a204e859cf0b67c223ce12/regex-2026.3.32-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:987cdfcfb97a249abc3601ad53c7de5c370529f1981e4c8c46793e4a1e1bfe8e", size = 913578, upload-time = "2026-03-28T21:47:08.172Z" }, - { url = "https://files.pythonhosted.org/packages/0f/f8/acf1eb80f58852e85bd39a6ddfa78ce2243ddc8de8da7582e6ba657da593/regex-2026.3.32-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5d88fa37ba5e8a80ca8d956b9ea03805cfa460223ac94b7d4854ee5e30f3173", size = 801536, upload-time = "2026-03-28T21:47:10.206Z" }, - { url = "https://files.pythonhosted.org/packages/9f/05/986cdf8d12693451f5889aaf4ea4f65b2c49b1152ae814fa1fb75439e40b/regex-2026.3.32-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d082be64e51671dd5ee1c208c92da2ddda0f2f20d8ef387e57634f7e97b6aae", size = 776226, upload-time = "2026-03-28T21:47:12.891Z" }, - { url = "https://files.pythonhosted.org/packages/32/02/945a6a2348ca1c6608cb1747275c8affd2ccd957d4885c25218a86377912/regex-2026.3.32-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1d7fa44aece1fa02b8927441614c96520253a5cad6a96994e3a81e060feed55", size = 785933, upload-time = "2026-03-28T21:47:14.795Z" }, - { url = "https://files.pythonhosted.org/packages/53/12/c5bab6cc679ad79a45427a98c4e70809586ac963c5ad54a9217533c4763e/regex-2026.3.32-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d478a2ca902b6ef28ffc9521e5f0f728d036abe35c0b250ee8ae78cfe7c5e44e", size = 860671, upload-time = "2026-03-28T21:47:16.985Z" }, - { url = "https://files.pythonhosted.org/packages/bf/68/8d85f98c2443469facabef62b82b851d369b13f92bec2ca7a3808deaa47b/regex-2026.3.32-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2820d2231885e97aff0fcf230a19ebd5d2b5b8a1ba338c20deb34f16db1c7897", size = 765335, upload-time = "2026-03-28T21:47:18.872Z" }, - { url = "https://files.pythonhosted.org/packages/89/a7/d8a9c270916107a501fca63b748547c6c77e570d19f16a29b557ce734f3d/regex-2026.3.32-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc8ced733d6cd9af5e412f256a32f7c61cd2d7371280a65c689939ac4572499f", size = 851913, upload-time = "2026-03-28T21:47:20.793Z" }, - { url = "https://files.pythonhosted.org/packages/f4/8e/03d392b26679914ccf21f83d18ad4443232d2f8c3e2c30a962d4e3918d9c/regex-2026.3.32-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:847087abe98b3c1ebf1eb49d6ef320dbba75a83ee4f83c94704580f1df007dd4", size = 788447, upload-time = "2026-03-28T21:47:22.628Z" }, - { url = "https://files.pythonhosted.org/packages/cf/df/692227d23535a50604333068b39eb262626db780ab1e1b19d83fc66853aa/regex-2026.3.32-cp313-cp313-win32.whl", hash = "sha256:d21a07edddb3e0ca12a8b8712abc8452481c3d3db19ae87fc94e9842d005964b", size = 266834, upload-time = "2026-03-28T21:47:24.778Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/13e4e56adc16ba607cffa1fe880f233eb9ded8ab8a8580619683c9e4ce48/regex-2026.3.32-cp313-cp313-win_amd64.whl", hash = "sha256:3c054e39a9f85a3d76c62a1d50c626c5e9306964eaa675c53f61ff7ec1204bbb", size = 277972, upload-time = "2026-03-28T21:47:26.627Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1c/80a86dbb2b416fec003b1801462bdcebbf1d43202ed5acb176e99c1ba369/regex-2026.3.32-cp313-cp313-win_arm64.whl", hash = "sha256:b2e9c2ea2e93223579308263f359eab8837dc340530b860cb59b713651889f14", size = 270649, upload-time = "2026-03-28T21:47:28.551Z" }, - { url = "https://files.pythonhosted.org/packages/58/08/e38372da599dc1c39c599907ec535016d110034bd3701ce36554f59767ef/regex-2026.3.32-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5d86e3fb08c94f084a625c8dc2132a79a3a111c8bf6e2bc59351fa61753c2f6e", size = 494495, upload-time = "2026-03-28T21:47:30.642Z" }, - { url = "https://files.pythonhosted.org/packages/5f/27/6e29ece8c9ce01001ece1137fa21c8707529c2305b22828f63623b0eb262/regex-2026.3.32-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b6f366a5ef66a2df4d9e68035cfe9f0eb8473cdfb922c37fac1d169b468607b0", size = 293988, upload-time = "2026-03-28T21:47:32.553Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/8752e18bb87a2fe728b73b0f83c082eb162a470766063f8028759fb26844/regex-2026.3.32-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b8fca73e16c49dd972ce3a88278dfa5b93bf91ddef332a46e9443abe21ca2f7c", size = 292634, upload-time = "2026-03-28T21:47:34.651Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7b/d7729fe294e23e9c7c3871cb69d49059fa7d65fd11e437a2cbea43f6615d/regex-2026.3.32-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b953d9d496d19786f4d46e6ba4b386c6e493e81e40f9c5392332458183b0599d", size = 810532, upload-time = "2026-03-28T21:47:36.839Z" }, - { url = "https://files.pythonhosted.org/packages/fd/49/4dae7b000659f611b17b9c1541fba800b0569e4060debc4635ef1b23982c/regex-2026.3.32-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b565f25171e04d4fad950d1fa837133e3af6ea6f509d96166eed745eb0cf63bc", size = 871919, upload-time = "2026-03-28T21:47:39.192Z" }, - { url = "https://files.pythonhosted.org/packages/83/85/aa8ad3977b9399861db3df62b33fe5fef6932ee23a1b9f4f357f58f2094b/regex-2026.3.32-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f28eac18a8733a124444643a66ac96fef2c0ad65f50034e0a043b90333dc677f", size = 916550, upload-time = "2026-03-28T21:47:41.618Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c0/6379d7f5b59ff0656ba49cf666d5013ecee55e83245275b310b0ffc79143/regex-2026.3.32-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cdd508664430dd51b8888deb6c5b416d8de046b2e11837254378d31febe4a98", size = 814988, upload-time = "2026-03-28T21:47:43.681Z" }, - { url = "https://files.pythonhosted.org/packages/2c/af/2dfddc64074bd9b70e27e170ee9db900542e2870210b489ad4471416ba86/regex-2026.3.32-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5c35d097f509cf7e40d20d5bee548d35d6049b36eb9965e8d43e4659923405b9", size = 786337, upload-time = "2026-03-28T21:47:46.076Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2f/4eb8abd705236402b4fe0e130971634deffb1855e2028bf02a2b7c0e841c/regex-2026.3.32-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:85c9b0c131427470a6423baa0a9330be6fd8c3630cc3ee6fdee03360724cbec5", size = 800029, upload-time = "2026-03-28T21:47:48.356Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2c/77d9ca2c9df483b51b4b1291c96d79c9ae301077841c4db39bc822f6b4c6/regex-2026.3.32-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e50af656c15e2723eeb7279c0837e07accc594b95ec18b86821a4d44b51b24bf", size = 865843, upload-time = "2026-03-28T21:47:50.762Z" }, - { url = "https://files.pythonhosted.org/packages/48/10/306f477a509f4eed699071b1f031d89edd5a2b5fa28c8ede5b2638eaba82/regex-2026.3.32-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4bc32b4dbdb4f9f300cf9f38f8ea2ce9511a068ffaa45ac1373ee7a943f1d810", size = 772473, upload-time = "2026-03-28T21:47:52.771Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f6/54bd83ec46ac037de2beb049afc9dd5d2769c6ecaadf7856254ce610e62a/regex-2026.3.32-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3e5d1802cba785210a4a800e63fcee7a228649a880f3bf7f2aadccb151a834b", size = 856805, upload-time = "2026-03-28T21:47:55.04Z" }, - { url = "https://files.pythonhosted.org/packages/37/e8/ee0e7d14de1fc6582d5782f072db6c61465a38a4142f88e175dda494b536/regex-2026.3.32-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ef250a3f5e93182193f5c927c5e9575b2cb14b80d03e258bc0b89cc5de076b60", size = 801875, upload-time = "2026-03-28T21:47:57.434Z" }, - { url = "https://files.pythonhosted.org/packages/8a/06/0fa9daca59d07b6aabd8e0468d3b86fd578576a157206fbcddbfc2298f7d/regex-2026.3.32-cp313-cp313t-win32.whl", hash = "sha256:9cf7036dfa2370ccc8651521fcbb40391974841119e9982fa312b552929e6c85", size = 269892, upload-time = "2026-03-28T21:47:59.674Z" }, - { url = "https://files.pythonhosted.org/packages/13/47/77f16b5ad9f10ca574f03d84a354b359b0ac33f85054f2f2daafc9f7b807/regex-2026.3.32-cp313-cp313t-win_amd64.whl", hash = "sha256:c940e00e8d3d10932c929d4b8657c2ea47d2560f31874c3e174c0d3488e8b865", size = 281318, upload-time = "2026-03-28T21:48:01.562Z" }, - { url = "https://files.pythonhosted.org/packages/c6/47/db4446faaea8d01c8315c9c89c7dc6abbb3305e8e712e9b23936095c4d58/regex-2026.3.32-cp313-cp313t-win_arm64.whl", hash = "sha256:ace48c5e157c1e58b7de633c5e257285ce85e567ac500c833349c363b3df69d4", size = 272366, upload-time = "2026-03-28T21:48:03.748Z" }, - { url = "https://files.pythonhosted.org/packages/32/68/ff024bf6131b7446a791a636dbbb7fa732d586f33b276d84b3460ea49393/regex-2026.3.32-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a416ee898ecbc5d8b283223b4cf4d560f93244f6f7615c1bd67359744b00c166", size = 490430, upload-time = "2026-03-28T21:48:05.654Z" }, - { url = "https://files.pythonhosted.org/packages/61/72/039d9164817ee298f2a2d0246001afe662241dcbec0eedd1fe03e2a2555e/regex-2026.3.32-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d76d62909bfb14521c3f7cfd5b94c0c75ec94b0a11f647d2f604998962ec7b6c", size = 291948, upload-time = "2026-03-28T21:48:07.666Z" }, - { url = "https://files.pythonhosted.org/packages/06/9d/77f684d90ffe3e99b828d3cabb87a0f1601d2b9decd1333ff345809b1d02/regex-2026.3.32-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:631f7d95c83f42bccfe18946a38ad27ff6b6717fb4807e60cf24860b5eb277fc", size = 289786, upload-time = "2026-03-28T21:48:09.562Z" }, - { url = "https://files.pythonhosted.org/packages/83/70/bd76069a0304e924682b2efd8683a01617a7e1da9b651af73039d8da76a4/regex-2026.3.32-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12917c6c6813ffcdfb11680a04e4d63c5532b88cf089f844721c5f41f41a63ad", size = 796672, upload-time = "2026-03-28T21:48:11.568Z" }, - { url = "https://files.pythonhosted.org/packages/80/31/c2d7d9a5671e111a2c16d57e0cb03e1ce35b28a115901590528aa928bb5b/regex-2026.3.32-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e221b615f83b15887636fcb90ed21f1a19541366f8b7ba14ba1ad8304f4ded4", size = 866556, upload-time = "2026-03-28T21:48:14.081Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b9/9921a31931d0bc3416ac30205471e0e2ed60dcbd16fc922bbd69b427322b/regex-2026.3.32-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f9ae4755fa90f1dc2d0d393d572ebc134c0fe30fcfc0ab7e67c1db15f192041", size = 912787, upload-time = "2026-03-28T21:48:16.548Z" }, - { url = "https://files.pythonhosted.org/packages/41/ab/2c1bc8ab99f63cdabdbc7823af8f4cfcd6ddbb2babf01861826c3f1ad44d/regex-2026.3.32-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a094e9dcafedfb9d333db5cf880304946683f43a6582bb86688f123335122929", size = 800879, upload-time = "2026-03-28T21:48:18.971Z" }, - { url = "https://files.pythonhosted.org/packages/49/e5/0be716eb2c0b2ae3a439e44432534e82b2f81848af64cb21c0473ad8ae46/regex-2026.3.32-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c1cecea3e477af105f32ef2119b8d895f297492e41d317e60d474bc4bffd62ff", size = 776332, upload-time = "2026-03-28T21:48:21.163Z" }, - { url = "https://files.pythonhosted.org/packages/26/80/114a61bd25dec7d1070930eaef82aadf9b05961a37629e7cca7bc3fc2257/regex-2026.3.32-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f26262900edd16272b6360014495e8d68379c6c6e95983f9b7b322dc928a1194", size = 786384, upload-time = "2026-03-28T21:48:23.277Z" }, - { url = "https://files.pythonhosted.org/packages/0c/78/be0a6531f8db426e8e60d6356aeef8e9cc3f541655a648c4968b63c87a88/regex-2026.3.32-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb22fa9ee6a0acb22fc9aecce5f9995fe4d2426ed849357d499d62608fbd7f9", size = 861381, upload-time = "2026-03-28T21:48:25.371Z" }, - { url = "https://files.pythonhosted.org/packages/45/b1/e5076fbe45b8fb39672584b1b606d512f5bd3a43155be68a95f6b88c1fc5/regex-2026.3.32-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9b9118a78e031a2e4709cd2fcc3028432e89b718db70073a8da574c249b5b249", size = 765434, upload-time = "2026-03-28T21:48:27.494Z" }, - { url = "https://files.pythonhosted.org/packages/a3/da/fd65d68b897f8b52b1390d20d776fa753582484724a9cb4f4c26de657ae5/regex-2026.3.32-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b193ed199848aa96618cd5959c1582a0bf23cd698b0b900cb0ffe81b02c8659c", size = 851501, upload-time = "2026-03-28T21:48:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/e8/d6/1e9c991c32022a9312e9124cc974961b3a2501338de2cd1cce75a3612d7a/regex-2026.3.32-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:10fb2aaae1aaadf7d43c9f3c2450404253697bf8b9ce360bd5418d1d16292298", size = 788076, upload-time = "2026-03-28T21:48:32.025Z" }, - { url = "https://files.pythonhosted.org/packages/f0/5b/b23c72f6d607cbb24ef42acf0c7c2ef4eee1377a9f7ba43b312f889edfbb/regex-2026.3.32-cp314-cp314-win32.whl", hash = "sha256:110ba4920721374d16c4c8ea7ce27b09546d43e16aea1d7f43681b5b8f80ba61", size = 272255, upload-time = "2026-03-28T21:48:34.355Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ec/32bbcc42366097a8cea2c481e02964be6c6fa5ccfb0fa9581686af0bec5f/regex-2026.3.32-cp314-cp314-win_amd64.whl", hash = "sha256:245667ad430745bae6a1e41081872d25819d86fbd9e0eec485ba00d9f78ad43d", size = 281160, upload-time = "2026-03-28T21:48:36.588Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e4/89038a028cb68e719fa03ab1ad603649fc199bcda12270d2ac7b471b8f5d/regex-2026.3.32-cp314-cp314-win_arm64.whl", hash = "sha256:1ca02ff0ef33e9d8276a1fcd6d90ff6ea055a32c9149c0050b5b67e26c6d2c51", size = 273688, upload-time = "2026-03-28T21:48:38.976Z" }, - { url = "https://files.pythonhosted.org/packages/30/6e/87caccd608837a1fa4f8c7edc48e206103452b9bbc94fc724fa39340e807/regex-2026.3.32-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:51fb7e26f91f9091fd8ec6a946f99b15d3bc3667cb5ddc73dd6cb2222dd4a1cc", size = 494506, upload-time = "2026-03-28T21:48:41.327Z" }, - { url = "https://files.pythonhosted.org/packages/16/53/a922e6b24694d70bdd68fc3fd076950e15b1b418cff9d2cc362b3968d86f/regex-2026.3.32-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:51a93452034d671b0e21b883d48ea66c5d6a05620ee16a9d3f229e828568f3f0", size = 293986, upload-time = "2026-03-28T21:48:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/60/e4/0cb32203c1aebad0577fcd5b9af1fe764869e617d5234bc6a0ad284299ea/regex-2026.3.32-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:03c2ebd15ff51e7b13bb3dc28dd5ac18cd39e59ebb40430b14ae1a19e833cff1", size = 292677, upload-time = "2026-03-28T21:48:45.772Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f8/5006b70291469d4174dd66ad162802e2f68419c0f2a7952d0c76c1288cfa/regex-2026.3.32-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bf2f3c2c5bd8360d335c7dcd4a9006cf1dabae063ee2558ee1b07bbc8a20d88", size = 810661, upload-time = "2026-03-28T21:48:48.147Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9b/438763a20d22cd1f65f95c8f030dd25df2d80a941068a891d21a5f240456/regex-2026.3.32-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a4a3189a99ecdd1c13f42513ab3fc7fa8311b38ba7596dd98537acb8cd9acc3", size = 872156, upload-time = "2026-03-28T21:48:50.739Z" }, - { url = "https://files.pythonhosted.org/packages/6c/5b/1341287887ac982ed9f5f60125e440513ffe354aa7e3681940495af7c12a/regex-2026.3.32-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c0bbfbd38506e1ea96a85da6782577f06239cb9fcf9696f1ea537c980c0680b", size = 916749, upload-time = "2026-03-28T21:48:53.57Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/1d2b48b8e94debfffc6fefb84d2a86a178cc208652a1d6493d5f29821c70/regex-2026.3.32-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8aaf8ee8f34b677f90742ca089b9c83d64bdc410528767273c816a863ed57327", size = 814788, upload-time = "2026-03-28T21:48:55.905Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d9/7dacb34c43adaeb954518d851f3e5d3ce495ac00a9d6010e3b4b59917c4a/regex-2026.3.32-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ea568832eca219c2be1721afa073c1c9eb8f98a9733fdedd0a9747639fc22a5", size = 786594, upload-time = "2026-03-28T21:48:58.404Z" }, - { url = "https://files.pythonhosted.org/packages/ea/72/28295068c92dbd6d3ce4fd22554345cf504e957cc57dadeda4a64fa86a57/regex-2026.3.32-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e4c8fa46aad1a11ae2f8fcd1c90b9d55e18925829ac0d98c5bb107f93351745", size = 800167, upload-time = "2026-03-28T21:49:01.226Z" }, - { url = "https://files.pythonhosted.org/packages/ca/17/b10745adeca5b8d52da050e7c746137f5d01dabc6dbbe6e8d9d821dc65c1/regex-2026.3.32-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cec365d44835b043d7b3266487797639d07d621bec9dc0ea224b00775797cc1", size = 865906, upload-time = "2026-03-28T21:49:03.484Z" }, - { url = "https://files.pythonhosted.org/packages/45/9d/1acbcce765044ac0c87f453f4876e0897f7a61c10315262f960184310798/regex-2026.3.32-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:09e26cad1544d856da85881ad292797289e4406338afe98163f3db9f7fac816c", size = 772642, upload-time = "2026-03-28T21:49:06.811Z" }, - { url = "https://files.pythonhosted.org/packages/24/41/1ef8b4811355ad7b9d7579d3aeca00f18b7bc043ace26c8c609b9287346d/regex-2026.3.32-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:6062c4ef581a3e9e503dccf4e1b7f2d33fdc1c13ad510b287741ac73bc4c6b27", size = 856927, upload-time = "2026-03-28T21:49:09.373Z" }, - { url = "https://files.pythonhosted.org/packages/97/b1/0dc1d361be80ec1b8b707ada041090181133a7a29d438e432260a4b26f9a/regex-2026.3.32-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88ebc0783907468f17fca3d7821b30f9c21865a721144eb498cb0ff99a67bcac", size = 801910, upload-time = "2026-03-28T21:49:11.818Z" }, - { url = "https://files.pythonhosted.org/packages/b5/db/1a23f767fa250844772a9464306d34e0fafe2c317303b88a1415096b6324/regex-2026.3.32-cp314-cp314t-win32.whl", hash = "sha256:e480d3dac06c89bc2e0fd87524cc38c546ac8b4a38177650745e64acbbcfdeba", size = 275714, upload-time = "2026-03-28T21:49:14.528Z" }, - { url = "https://files.pythonhosted.org/packages/c2/2b/616d31b125ca76079d74d6b1d84ec0860ffdb41c379151135d06e35a8633/regex-2026.3.32-cp314-cp314t-win_amd64.whl", hash = "sha256:67015a8162d413af9e3309d9a24e385816666fbf09e48e3ec43342c8536f7df6", size = 285722, upload-time = "2026-03-28T21:49:16.642Z" }, - { url = "https://files.pythonhosted.org/packages/7e/91/043d9a00d6123c5fa22a3dc96b10445ce434a8110e1d5e53efb01f243c8b/regex-2026.3.32-cp314-cp314t-win_arm64.whl", hash = "sha256:1a6ac1ed758902e664e0d95c1ee5991aa6fb355423f378ed184c6ec47a1ec0e9", size = 275700, upload-time = "2026-03-28T21:49:19.348Z" }, +version = "2026.2.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/b8/845a927e078f5e5cc55d29f57becbfde0003d52806544531ab3f2da4503c/regex-2026.2.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc48c500838be6882b32748f60a15229d2dea96e59ef341eaa96ec83538f498d", size = 488461, upload-time = "2026-02-28T02:15:48.405Z" }, + { url = "https://files.pythonhosted.org/packages/32/f9/8a0034716684e38a729210ded6222249f29978b24b684f448162ef21f204/regex-2026.2.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2afa673660928d0b63d84353c6c08a8a476ddfc4a47e11742949d182e6863ce8", size = 290774, upload-time = "2026-02-28T02:15:51.738Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ba/b27feefffbb199528dd32667cd172ed484d9c197618c575f01217fbe6103/regex-2026.2.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7ab218076eb0944549e7fe74cf0e2b83a82edb27e81cc87411f76240865e04d5", size = 288737, upload-time = "2026-02-28T02:15:53.534Z" }, + { url = "https://files.pythonhosted.org/packages/18/c5/65379448ca3cbfe774fcc33774dc8295b1ee97dc3237ae3d3c7b27423c9d/regex-2026.2.28-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d63db12e45a9b9f064bfe4800cefefc7e5f182052e4c1b774d46a40ab1d9bb", size = 782675, upload-time = "2026-02-28T02:15:55.488Z" }, + { url = "https://files.pythonhosted.org/packages/aa/30/6fa55bef48090f900fbd4649333791fc3e6467380b9e775e741beeb3231f/regex-2026.2.28-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:195237dc327858a7721bf8b0bbbef797554bc13563c3591e91cd0767bacbe359", size = 850514, upload-time = "2026-02-28T02:15:57.509Z" }, + { url = "https://files.pythonhosted.org/packages/a9/28/9ca180fb3787a54150209754ac06a42409913571fa94994f340b3bba4e1e/regex-2026.2.28-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b387a0d092dac157fb026d737dde35ff3e49ef27f285343e7c6401851239df27", size = 896612, upload-time = "2026-02-28T02:15:59.682Z" }, + { url = "https://files.pythonhosted.org/packages/46/b5/f30d7d3936d6deecc3ea7bea4f7d3c5ee5124e7c8de372226e436b330a55/regex-2026.2.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3935174fa4d9f70525a4367aaff3cb8bc0548129d114260c29d9dfa4a5b41692", size = 791691, upload-time = "2026-02-28T02:16:01.752Z" }, + { url = "https://files.pythonhosted.org/packages/f5/34/96631bcf446a56ba0b2a7f684358a76855dfe315b7c2f89b35388494ede0/regex-2026.2.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b2b23587b26496ff5fd40df4278becdf386813ec00dc3533fa43a4cf0e2ad3c", size = 783111, upload-time = "2026-02-28T02:16:03.651Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/f95cb7a85fe284d41cd2f3625e0f2ae30172b55dfd2af1d9b4eaef6259d7/regex-2026.2.28-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3b24bd7e9d85dc7c6a8bd2aa14ecd234274a0248335a02adeb25448aecdd420d", size = 767512, upload-time = "2026-02-28T02:16:05.616Z" }, + { url = "https://files.pythonhosted.org/packages/3d/af/a650f64a79c02a97f73f64d4e7fc4cc1984e64affab14075e7c1f9a2db34/regex-2026.2.28-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd477d5f79920338107f04aa645f094032d9e3030cc55be581df3d1ef61aa318", size = 773920, upload-time = "2026-02-28T02:16:08.325Z" }, + { url = "https://files.pythonhosted.org/packages/72/f8/3f9c2c2af37aedb3f5a1e7227f81bea065028785260d9cacc488e43e6997/regex-2026.2.28-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b49eb78048c6354f49e91e4b77da21257fecb92256b6d599ae44403cab30b05b", size = 846681, upload-time = "2026-02-28T02:16:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/8db04a334571359f4d127d8f89550917ec6561a2fddfd69cd91402b47482/regex-2026.2.28-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a25c7701e4f7a70021db9aaf4a4a0a67033c6318752146e03d1b94d32006217e", size = 755565, upload-time = "2026-02-28T02:16:11.972Z" }, + { url = "https://files.pythonhosted.org/packages/da/bc/91c22f384d79324121b134c267a86ca90d11f8016aafb1dc5bee05890ee3/regex-2026.2.28-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9dd450db6458387167e033cfa80887a34c99c81d26da1bf8b0b41bf8c9cac88e", size = 835789, upload-time = "2026-02-28T02:16:14.036Z" }, + { url = "https://files.pythonhosted.org/packages/46/a7/4cc94fd3af01dcfdf5a9ed75c8e15fd80fcd62cc46da7592b1749e9c35db/regex-2026.2.28-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2954379dd20752e82d22accf3ff465311cbb2bac6c1f92c4afd400e1757f7451", size = 780094, upload-time = "2026-02-28T02:16:15.468Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/e5a38f420af3c77cab4a65f0c3a55ec02ac9babf04479cfd282d356988a6/regex-2026.2.28-cp310-cp310-win32.whl", hash = "sha256:1f8b17be5c27a684ea6759983c13506bd77bfc7c0347dff41b18ce5ddd2ee09a", size = 266025, upload-time = "2026-02-28T02:16:16.828Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0a/205c4c1466a36e04d90afcd01d8908bac327673050c7fe316b2416d99d3d/regex-2026.2.28-cp310-cp310-win_amd64.whl", hash = "sha256:dd8847c4978bc3c7e6c826fb745f5570e518b8459ac2892151ce6627c7bc00d5", size = 277965, upload-time = "2026-02-28T02:16:18.752Z" }, + { url = "https://files.pythonhosted.org/packages/c3/4d/29b58172f954b6ec2c5ed28529a65e9026ab96b4b7016bcd3858f1c31d3c/regex-2026.2.28-cp310-cp310-win_arm64.whl", hash = "sha256:73cdcdbba8028167ea81490c7f45280113e41db2c7afb65a276f4711fa3bcbff", size = 270336, upload-time = "2026-02-28T02:16:20.735Z" }, + { url = "https://files.pythonhosted.org/packages/04/db/8cbfd0ba3f302f2d09dd0019a9fcab74b63fee77a76c937d0e33161fb8c1/regex-2026.2.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e621fb7c8dc147419b28e1702f58a0177ff8308a76fa295c71f3e7827849f5d9", size = 488462, upload-time = "2026-02-28T02:16:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/5d/10/ccc22c52802223f2368731964ddd117799e1390ffc39dbb31634a83022ee/regex-2026.2.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d5bef2031cbf38757a0b0bc4298bb4824b6332d28edc16b39247228fbdbad97", size = 290774, upload-time = "2026-02-28T02:16:23.993Z" }, + { url = "https://files.pythonhosted.org/packages/62/b9/6796b3bf3101e64117201aaa3a5a030ec677ecf34b3cd6141b5d5c6c67d5/regex-2026.2.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcb399ed84eabf4282587ba151f2732ad8168e66f1d3f85b1d038868fe547703", size = 288724, upload-time = "2026-02-28T02:16:25.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/02/291c0ae3f3a10cea941d0f5366da1843d8d1fa8a25b0671e20a0e454bb38/regex-2026.2.28-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c1b34dfa72f826f535b20712afa9bb3ba580020e834f3c69866c5bddbf10098", size = 791924, upload-time = "2026-02-28T02:16:26.863Z" }, + { url = "https://files.pythonhosted.org/packages/0f/57/f0235cc520d9672742196c5c15098f8f703f2758d48d5a7465a56333e496/regex-2026.2.28-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:851fa70df44325e1e4cdb79c5e676e91a78147b1b543db2aec8734d2add30ec2", size = 860095, upload-time = "2026-02-28T02:16:28.772Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7c/393c94cbedda79a0f5f2435ebd01644aba0b338d327eb24b4aa5b8d6c07f/regex-2026.2.28-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:516604edd17b1c2c3e579cf4e9b25a53bf8fa6e7cedddf1127804d3e0140ca64", size = 906583, upload-time = "2026-02-28T02:16:30.977Z" }, + { url = "https://files.pythonhosted.org/packages/2c/73/a72820f47ca5abf2b5d911d0407ba5178fc52cf9780191ed3a54f5f419a2/regex-2026.2.28-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7ce83654d1ab701cb619285a18a8e5a889c1216d746ddc710c914ca5fd71022", size = 800234, upload-time = "2026-02-28T02:16:32.55Z" }, + { url = "https://files.pythonhosted.org/packages/34/b3/6e6a4b7b31fa998c4cf159a12cbeaf356386fbd1a8be743b1e80a3da51e4/regex-2026.2.28-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2791948f7c70bb9335a9102df45e93d428f4b8128020d85920223925d73b9e1", size = 772803, upload-time = "2026-02-28T02:16:34.029Z" }, + { url = "https://files.pythonhosted.org/packages/10/e7/5da0280c765d5a92af5e1cd324b3fe8464303189cbaa449de9a71910e273/regex-2026.2.28-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a83cc26aa2acda6b8b9dfe748cf9e84cbd390c424a1de34fdcef58961a297a", size = 781117, upload-time = "2026-02-28T02:16:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/76/39/0b8d7efb256ae34e1b8157acc1afd8758048a1cf0196e1aec2e71fd99f4b/regex-2026.2.28-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ec6f5674c5dc836994f50f1186dd1fafde4be0666aae201ae2fcc3d29d8adf27", size = 854224, upload-time = "2026-02-28T02:16:38.119Z" }, + { url = "https://files.pythonhosted.org/packages/21/ff/a96d483ebe8fe6d1c67907729202313895d8de8495569ec319c6f29d0438/regex-2026.2.28-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:50c2fc924749543e0eacc93ada6aeeb3ea5f6715825624baa0dccaec771668ae", size = 761898, upload-time = "2026-02-28T02:16:40.333Z" }, + { url = "https://files.pythonhosted.org/packages/89/bd/d4f2e75cb4a54b484e796017e37c0d09d8a0a837de43d17e238adf163f4e/regex-2026.2.28-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ba55c50f408fb5c346a3a02d2ce0ebc839784e24f7c9684fde328ff063c3cdea", size = 844832, upload-time = "2026-02-28T02:16:41.875Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/428a135cf5e15e4e11d1e696eb2bf968362f8ea8a5f237122e96bc2ae950/regex-2026.2.28-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edb1b1b3a5576c56f08ac46f108c40333f222ebfd5cf63afdfa3aab0791ebe5b", size = 788347, upload-time = "2026-02-28T02:16:43.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/59/68691428851cf9c9c3707217ab1d9b47cfeec9d153a49919e6c368b9e926/regex-2026.2.28-cp311-cp311-win32.whl", hash = "sha256:948c12ef30ecedb128903c2c2678b339746eb7c689c5c21957c4a23950c96d15", size = 266033, upload-time = "2026-02-28T02:16:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/1483de1c57024e89296cbcceb9cccb3f625d416ddb46e570be185c9b05a9/regex-2026.2.28-cp311-cp311-win_amd64.whl", hash = "sha256:fd63453f10d29097cc3dc62d070746523973fb5aa1c66d25f8558bebd47fed61", size = 277978, upload-time = "2026-02-28T02:16:46.75Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/abec45dc6e7252e3dbc797120496e43bb5730a7abf0d9cb69340696a2f2d/regex-2026.2.28-cp311-cp311-win_arm64.whl", hash = "sha256:00f2b8d9615aa165fdff0a13f1a92049bfad555ee91e20d246a51aa0b556c60a", size = 270340, upload-time = "2026-02-28T02:16:48.626Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574, upload-time = "2026-02-28T02:16:50.455Z" }, + { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426, upload-time = "2026-02-28T02:16:52.52Z" }, + { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200, upload-time = "2026-02-28T02:16:54.08Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765, upload-time = "2026-02-28T02:16:55.905Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093, upload-time = "2026-02-28T02:16:58.094Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455, upload-time = "2026-02-28T02:17:00.918Z" }, + { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037, upload-time = "2026-02-28T02:17:02.842Z" }, + { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113, upload-time = "2026-02-28T02:17:04.506Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194, upload-time = "2026-02-28T02:17:06.888Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846, upload-time = "2026-02-28T02:17:09.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516, upload-time = "2026-02-28T02:17:11.004Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278, upload-time = "2026-02-28T02:17:12.693Z" }, + { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068, upload-time = "2026-02-28T02:17:14.9Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416, upload-time = "2026-02-28T02:17:17.15Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297, upload-time = "2026-02-28T02:17:18.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408, upload-time = "2026-02-28T02:17:20.328Z" }, + { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311, upload-time = "2026-02-28T02:17:22.591Z" }, + { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285, upload-time = "2026-02-28T02:17:24.355Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051, upload-time = "2026-02-28T02:17:26.722Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842, upload-time = "2026-02-28T02:17:29.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083, upload-time = "2026-02-28T02:17:31.363Z" }, + { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412, upload-time = "2026-02-28T02:17:33.248Z" }, + { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101, upload-time = "2026-02-28T02:17:35.053Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260, upload-time = "2026-02-28T02:17:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311, upload-time = "2026-02-28T02:17:39.855Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876, upload-time = "2026-02-28T02:17:42.317Z" }, + { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632, upload-time = "2026-02-28T02:17:45.073Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320, upload-time = "2026-02-28T02:17:47.192Z" }, + { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152, upload-time = "2026-02-28T02:17:49.067Z" }, + { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398, upload-time = "2026-02-28T02:17:50.744Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282, upload-time = "2026-02-28T02:17:53.074Z" }, + { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382, upload-time = "2026-02-28T02:17:54.888Z" }, + { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541, upload-time = "2026-02-28T02:17:56.813Z" }, + { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984, upload-time = "2026-02-28T02:17:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509, upload-time = "2026-02-28T02:18:00.208Z" }, + { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429, upload-time = "2026-02-28T02:18:02.328Z" }, + { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422, upload-time = "2026-02-28T02:18:04.23Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175, upload-time = "2026-02-28T02:18:06.791Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044, upload-time = "2026-02-28T02:18:08.736Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056, upload-time = "2026-02-28T02:18:10.777Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743, upload-time = "2026-02-28T02:18:13.025Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633, upload-time = "2026-02-28T02:18:16.84Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862, upload-time = "2026-02-28T02:18:18.892Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788, upload-time = "2026-02-28T02:18:21.475Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184, upload-time = "2026-02-28T02:18:23.492Z" }, + { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137, upload-time = "2026-02-28T02:18:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682, upload-time = "2026-02-28T02:18:27.205Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735, upload-time = "2026-02-28T02:18:29.015Z" }, + { url = "https://files.pythonhosted.org/packages/cf/03/691015f7a7cb1ed6dacb2ea5de5682e4858e05a4c5506b2839cd533bbcd6/regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc", size = 489497, upload-time = "2026-02-28T02:18:30.889Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ba/8db8fd19afcbfa0e1036eaa70c05f20ca8405817d4ad7a38a6b4c2f031ac/regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd", size = 291295, upload-time = "2026-02-28T02:18:33.426Z" }, + { url = "https://files.pythonhosted.org/packages/5a/79/9aa0caf089e8defef9b857b52fc53801f62ff868e19e5c83d4a96612eba1/regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff", size = 289275, upload-time = "2026-02-28T02:18:35.247Z" }, + { url = "https://files.pythonhosted.org/packages/eb/26/ee53117066a30ef9c883bf1127eece08308ccf8ccd45c45a966e7a665385/regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911", size = 797176, upload-time = "2026-02-28T02:18:37.15Z" }, + { url = "https://files.pythonhosted.org/packages/05/1b/67fb0495a97259925f343ae78b5d24d4a6624356ae138b57f18bd43006e4/regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33", size = 863813, upload-time = "2026-02-28T02:18:39.478Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/93ac9bbafc53618091c685c7ed40239a90bf9f2a82c983f0baa97cb7ae07/regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117", size = 908678, upload-time = "2026-02-28T02:18:41.619Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/a8f5e0561702b25239846a16349feece59712ae20598ebb205580332a471/regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d", size = 801528, upload-time = "2026-02-28T02:18:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/96/5d/ed6d4cbde80309854b1b9f42d9062fee38ade15f7eb4909f6ef2440403b5/regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a", size = 775373, upload-time = "2026-02-28T02:18:46.102Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e9/6e53c34e8068b9deec3e87210086ecb5b9efebdefca6b0d3fa43d66dcecb/regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf", size = 784859, upload-time = "2026-02-28T02:18:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/736e1c7ca7f0dcd2ae33819888fdc69058a349b7e5e84bc3e2f296bbf794/regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952", size = 857813, upload-time = "2026-02-28T02:18:50.576Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7c/48c4659ad9da61f58e79dbe8c05223e0006696b603c16eb6b5cbfbb52c27/regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8", size = 763705, upload-time = "2026-02-28T02:18:52.59Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a1/bc1c261789283128165f71b71b4b221dd1b79c77023752a6074c102f18d8/regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07", size = 848734, upload-time = "2026-02-28T02:18:54.595Z" }, + { url = "https://files.pythonhosted.org/packages/10/d8/979407faf1397036e25a5ae778157366a911c0f382c62501009f4957cf86/regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6", size = 789871, upload-time = "2026-02-28T02:18:57.34Z" }, + { url = "https://files.pythonhosted.org/packages/03/23/da716821277115fcb1f4e3de1e5dc5023a1e6533598c486abf5448612579/regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6", size = 271825, upload-time = "2026-02-28T02:18:59.202Z" }, + { url = "https://files.pythonhosted.org/packages/91/ff/90696f535d978d5f16a52a419be2770a8d8a0e7e0cfecdbfc31313df7fab/regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7", size = 280548, upload-time = "2026-02-28T02:19:01.049Z" }, + { url = "https://files.pythonhosted.org/packages/69/f9/5e1b5652fc0af3fcdf7677e7df3ad2a0d47d669b34ac29a63bb177bb731b/regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d", size = 273444, upload-time = "2026-02-28T02:19:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/d3/eb/8389f9e940ac89bcf58d185e230a677b4fd07c5f9b917603ad5c0f8fa8fe/regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e", size = 492546, upload-time = "2026-02-28T02:19:05.378Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c7/09441d27ce2a6fa6a61ea3150ea4639c1dcda9b31b2ea07b80d6937b24dd/regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c", size = 292986, upload-time = "2026-02-28T02:19:07.24Z" }, + { url = "https://files.pythonhosted.org/packages/fb/69/4144b60ed7760a6bd235e4087041f487aa4aa62b45618ce018b0c14833ea/regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7", size = 291518, upload-time = "2026-02-28T02:19:09.698Z" }, + { url = "https://files.pythonhosted.org/packages/2d/be/77e5426cf5948c82f98c53582009ca9e94938c71f73a8918474f2e2990bb/regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e", size = 809464, upload-time = "2026-02-28T02:19:12.494Z" }, + { url = "https://files.pythonhosted.org/packages/45/99/2c8c5ac90dc7d05c6e7d8e72c6a3599dc08cd577ac476898e91ca787d7f1/regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc", size = 869553, upload-time = "2026-02-28T02:19:15.151Z" }, + { url = "https://files.pythonhosted.org/packages/53/34/daa66a342f0271e7737003abf6c3097aa0498d58c668dbd88362ef94eb5d/regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8", size = 915289, upload-time = "2026-02-28T02:19:17.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c7/e22c2aaf0a12e7e22ab19b004bb78d32ca1ecc7ef245949935463c5567de/regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0", size = 812156, upload-time = "2026-02-28T02:19:20.011Z" }, + { url = "https://files.pythonhosted.org/packages/7f/bb/2dc18c1efd9051cf389cd0d7a3a4d90f6804b9fff3a51b5dc3c85b935f71/regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b", size = 782215, upload-time = "2026-02-28T02:19:22.047Z" }, + { url = "https://files.pythonhosted.org/packages/17/1e/9e4ec9b9013931faa32226ec4aa3c71fe664a6d8a2b91ac56442128b332f/regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b", size = 798925, upload-time = "2026-02-28T02:19:24.173Z" }, + { url = "https://files.pythonhosted.org/packages/71/57/a505927e449a9ccb41e2cc8d735e2abe3444b0213d1cf9cb364a8c1f2524/regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033", size = 864701, upload-time = "2026-02-28T02:19:26.376Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ad/c62cb60cdd93e13eac5b3d9d6bd5d284225ed0e3329426f94d2552dd7cca/regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43", size = 770899, upload-time = "2026-02-28T02:19:29.38Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5a/874f861f5c3d5ab99633e8030dee1bc113db8e0be299d1f4b07f5b5ec349/regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18", size = 854727, upload-time = "2026-02-28T02:19:31.494Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ca/d2c03b0efde47e13db895b975b2be6a73ed90b8ba963677927283d43bf74/regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a", size = 800366, upload-time = "2026-02-28T02:19:34.248Z" }, + { url = "https://files.pythonhosted.org/packages/14/bd/ee13b20b763b8989f7c75d592bfd5de37dc1181814a2a2747fedcf97e3ba/regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e", size = 274936, upload-time = "2026-02-28T02:19:36.313Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e7/d8020e39414c93af7f0d8688eabcecece44abfd5ce314b21dfda0eebd3d8/regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9", size = 284779, upload-time = "2026-02-28T02:19:38.625Z" }, + { url = "https://files.pythonhosted.org/packages/13/c0/ad225f4a405827486f1955283407cf758b6d2fb966712644c5f5aef33d1b/regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec", size = 275010, upload-time = "2026-02-28T02:19:40.65Z" }, ] [[package]] name = "requests" -version = "2.33.0" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1767,9 +1771,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -1908,27 +1912,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] @@ -2012,56 +2016,56 @@ wheels = [ [[package]] name = "tomli" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, - { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, - { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, - { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, - { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, - { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, - { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, - { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, - { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, - { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, - { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, - { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, - { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] @@ -2153,14 +2157,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.7" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/43/76ded108b296a49f52de6bac5192ca1c4be84e886f9b5c9ba8427d9694fd/werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351", size = 875700, upload-time = "2026-03-24T01:08:07.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, ] [[package]] From 7edd17ebae2664870bdae9cbbbced3219dd2fa04 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Tue, 31 Mar 2026 11:26:41 +0200 Subject: [PATCH 233/296] ci(dependabot): add github-actions ecosystem with grouped updates (#1597) Co-authored-by: Claude Opus 4.6 (1M context) --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f949dfb74..61538b021 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,3 +21,17 @@ updates: llama-index: patterns: - "llama-index*" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + rebase-strategy: "disabled" + commit-message: + prefix: chore + prefix-development: chore + include: scope + groups: + github-actions: + patterns: + - "*" From 0ddf3bcf786ddf86f58f0fca006ba56febebe046 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Tue, 31 Mar 2026 13:30:17 +0200 Subject: [PATCH 234/296] ci: pin and bump GH actions (#1596) --- .github/workflows/ci.yml | 20 +++++++++---------- .../claude-review-maintainer-prs.yml | 4 ++-- .github/workflows/codeql.yml | 6 +++--- .github/workflows/dependabot-merge.yml | 2 +- .github/workflows/dependabot-rebase-stale.yml | 2 +- .../workflows/package-availability-check.yml | 2 +- .github/workflows/release.yml | 20 +++++++++---------- 7 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68c3dc977..25197e629 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,9 @@ jobs: linting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install uv and set Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.13" @@ -33,14 +33,14 @@ jobs: type-checking: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install uv and set Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.13" enable-cache: true - - uses: actions/cache@v3 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 name: Cache mypy cache with: path: ./.mypy_cache @@ -75,8 +75,8 @@ jobs: name: Test on Python version ${{ matrix.python-version }} steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 with: version: 10.33.0 @@ -85,12 +85,12 @@ jobs: git clone https://github.com/langfuse/langfuse.git ./langfuse-server && echo $(cd ./langfuse-server && git rev-parse HEAD) - name: Setup node (for langfuse server) - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: node-version: 24 - name: Cache langfuse server dependencies - uses: actions/cache@v3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: ./langfuse-server/node_modules key: | @@ -155,7 +155,7 @@ jobs: echo "Langfuse server is up and running!" - name: Install uv and set Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/claude-review-maintainer-prs.yml b/.github/workflows/claude-review-maintainer-prs.yml index 016559888..50193b89e 100644 --- a/.github/workflows/claude-review-maintainer-prs.yml +++ b/.github/workflows/claude-review-maintainer-prs.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Check author permission and existing review request id: check - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const owner = context.repo.owner; @@ -57,7 +57,7 @@ jobs: - name: Add Claude review comment if: steps.check.outputs.should_comment == 'true' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | await github.rest.issues.createComment({ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 92cc5c1fc..02d502eb7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -55,11 +55,11 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -87,6 +87,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 043b7198d..1a86ae8f8 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1 + uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs diff --git a/.github/workflows/dependabot-rebase-stale.yml b/.github/workflows/dependabot-rebase-stale.yml index 79d85964c..dcbe57211 100644 --- a/.github/workflows/dependabot-rebase-stale.yml +++ b/.github/workflows/dependabot-rebase-stale.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Rebase open Dependabot PR" - uses: orange-buffalo/dependabot-auto-rebase@v1 + uses: orange-buffalo/dependabot-auto-rebase@fa9e05d7a8152381af0a92ffca942a0d46712544 # v1 with: api-token: ${{ secrets.DEP_REBASE_PAT }} repository: ${{ github.repository }} diff --git a/.github/workflows/package-availability-check.yml b/.github/workflows/package-availability-check.yml index 70c53942b..e8074a9ba 100644 --- a/.github/workflows/package-availability-check.yml +++ b/.github/workflows/package-availability-check.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies using pip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af16d7927..e24bd45a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,13 +62,13 @@ jobs: fi - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 token: ${{ secrets.GH_ACCESS_TOKEN }} - name: Install uv and set Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.12" @@ -285,7 +285,7 @@ jobs: - name: Create GitHub Release id: create-release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 with: tag_name: v${{ steps.new-version.outputs.version }} name: v${{ steps.new-version.outputs.version }} @@ -299,8 +299,10 @@ jobs: - name: Notify Slack on success if: success() - uses: slackapi/slack-github-action@v1.26.0 + uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3 with: + webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} + webhook-type: incoming-webhook payload: | { "text": "✅ Langfuse Python SDK v${{ steps.new-version.outputs.version }} published to PyPI", @@ -378,14 +380,13 @@ jobs: } ] } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_RELEASES }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - name: Notify Slack on failure if: failure() - uses: slackapi/slack-github-action@v1.26.0 + uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3 with: + webhook: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} + webhook-type: incoming-webhook payload: | { "text": "❌ Langfuse Python SDK release workflow failed", @@ -471,6 +472,3 @@ jobs: } ] } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK From a3f15c603ac662b634cf4d6b301e5ec060e425f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:47:44 +0200 Subject: [PATCH 235/296] chore(deps): bump the github-actions group across 1 directory with 3 updates (#1599) Bumps the github-actions group with 3 updates in the / directory: [actions/setup-node](https://github.com/actions/setup-node), [actions/github-script](https://github.com/actions/github-script) and [github/codeql-action](https://github.com/github/codeql-action). Updates `actions/setup-node` from 3.9.1 to 6.3.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/3235b876344d2a9aa001b8d1453c930bba69e610...53b83947a5a98c8d113130e565377fae1a50d02f) Updates `actions/github-script` from 7.1.0 to 8.0.0 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/f28e40c7f34bde8b3046d885e986cb6290c5673b...ed597411d8f924073f98dfc5c65a23a2325f34cd) Updates `github/codeql-action` from 3.35.1 to 4.35.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5c8a8a642e79153f5d047b10ec1cba1d1cc65699...c10b8064de6f491fea524254123dbe5e09572f13) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/github-script dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: github/codeql-action dependency-version: 4.35.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/claude-review-maintainer-prs.yml | 4 ++-- .github/workflows/codeql.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25197e629..d81ae3f99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: git clone https://github.com/langfuse/langfuse.git ./langfuse-server && echo $(cd ./langfuse-server && git rev-parse HEAD) - name: Setup node (for langfuse server) - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 24 diff --git a/.github/workflows/claude-review-maintainer-prs.yml b/.github/workflows/claude-review-maintainer-prs.yml index 50193b89e..977ff4a4b 100644 --- a/.github/workflows/claude-review-maintainer-prs.yml +++ b/.github/workflows/claude-review-maintainer-prs.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Check author permission and existing review request id: check - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const owner = context.repo.owner; @@ -57,7 +57,7 @@ jobs: - name: Add Claude review comment if: steps.check.outputs.should_comment == 'true' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | await github.rest.issues.createComment({ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 02d502eb7..19d682989 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -59,7 +59,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -87,6 +87,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: category: "/language:${{matrix.language}}" From e32aeb2d928bc4db14b5a810595c9f10251ecd52 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:56:46 +0200 Subject: [PATCH 236/296] fix(experiments): fix unstable local experiment IDs for local data (#1600) --- langfuse/_client/client.py | 17 +++++++++-- langfuse/experiment.py | 6 ++++ tests/test_propagate_attributes.py | 49 ++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 9beed5f67..85ec83a4e 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -2427,6 +2427,7 @@ def run_experiment( - run_name: The experiment run name. This is equal to the dataset run name if experiment was on Langfuse dataset. - item_results: List of results for each processed item with outputs and evaluations - run_evaluations: List of aggregate evaluation results for the entire run + - experiment_id: Stable identifier for the experiment run across all items - dataset_run_id: ID of the dataset run (if using Langfuse datasets) - dataset_run_url: Direct URL to view results in Langfuse UI (if applicable) @@ -2577,6 +2578,8 @@ async def _run_experiment_async( f"Starting experiment '{name}' run '{run_name}' with {len(data)} items" ) + shared_fallback_experiment_id = self._create_observation_id() + # Set up concurrency control semaphore = asyncio.Semaphore(max_concurrency) @@ -2588,6 +2591,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult: task, evaluators, composite_evaluator, + shared_fallback_experiment_id, name, run_name, description, @@ -2619,7 +2623,14 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult: langfuse_logger.error(f"Run evaluator failed: {e}") # Generate dataset run URL if applicable - dataset_run_id = valid_results[0].dataset_run_id if valid_results else None + dataset_run_id = next( + ( + result.dataset_run_id + for result in valid_results + if result.dataset_run_id + ), + None, + ) dataset_run_url = None if dataset_run_id and data: try: @@ -2665,6 +2676,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult: description=description, item_results=valid_results, run_evaluations=run_evaluations, + experiment_id=dataset_run_id or shared_fallback_experiment_id, dataset_run_id=dataset_run_id, dataset_run_url=dataset_run_url, ) @@ -2675,6 +2687,7 @@ async def _process_experiment_item( task: Callable, evaluators: List[Callable], composite_evaluator: Optional[CompositeEvaluatorFunction], + fallback_experiment_id: str, experiment_name: str, experiment_run_name: str, experiment_description: Optional[str], @@ -2753,7 +2766,7 @@ async def _process_experiment_item( if isinstance(item_metadata, dict): final_observation_metadata.update(item_metadata) - experiment_id = dataset_run_id or self._create_observation_id() + experiment_id = dataset_run_id or fallback_experiment_id experiment_item_id = ( dataset_item_id or get_sha256_hash_hex(_serialize(input_data))[:16] ) diff --git a/langfuse/experiment.py b/langfuse/experiment.py index ff707fc9b..6e4b32e10 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -303,6 +303,9 @@ class ExperimentResult: containing the original item, task output, evaluations, and trace information. run_evaluations: List of aggregate evaluation results computed across all items, such as average scores, statistical summaries, or cross-item analyses. + experiment_id: ID of the experiment run propagated across all items. For + Langfuse datasets, this matches the dataset run ID. For local experiments, + this is a stable SDK-generated identifier for the run. dataset_run_id: Optional ID of the dataset run in Langfuse (when using Langfuse datasets). dataset_run_url: Optional direct URL to view the experiment results in Langfuse UI. @@ -361,6 +364,7 @@ def __init__( description: Optional[str], item_results: List[ExperimentItemResult], run_evaluations: List[Evaluation], + experiment_id: str, dataset_run_id: Optional[str] = None, dataset_run_url: Optional[str] = None, ): @@ -372,6 +376,7 @@ def __init__( description: Optional description of the experiment. item_results: List of results from processing individual dataset items. run_evaluations: List of aggregate evaluation results for the entire run. + experiment_id: ID of the experiment run. dataset_run_id: Optional ID of the dataset run (for Langfuse datasets). dataset_run_url: Optional URL to view results in Langfuse UI. """ @@ -380,6 +385,7 @@ def __init__( self.description = description self.item_results = item_results self.run_evaluations = run_evaluations + self.experiment_id = experiment_id self.dataset_run_id = dataset_run_id self.dataset_run_url = dataset_run_url diff --git a/tests/test_propagate_attributes.py b/tests/test_propagate_attributes.py index b3be9f830..d771a3d74 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/test_propagate_attributes.py @@ -2384,6 +2384,7 @@ def task_with_child_spans(*, item, **kwargs): experiment_id = first_root["attributes"][ LangfuseOtelSpanAttributes.EXPERIMENT_ID ] + assert result.experiment_id == experiment_id experiment_item_id = first_root["attributes"][ LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID ] @@ -2478,25 +2479,55 @@ def task_with_child_spans(*, item, **kwargs): LangfuseOtelSpanAttributes.EXPERIMENT_DATASET_ID, ) + def test_experiment_id_is_stable_across_local_items( + self, langfuse_client, memory_exporter + ): + """Test local experiments reuse one experiment ID across all items.""" + local_data = [ + {"input": "test input 1", "expected_output": "expected result 1"}, + {"input": "test input 2", "expected_output": "expected result 2"}, + ] + + result = langfuse_client.run_experiment( + name="Stable Local Experiment", + data=local_data, + task=lambda *, item, **kwargs: f"processed: {item['input']}", + ) + + langfuse_client.flush() + time.sleep(0.1) + + root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") + experiment_ids = { + span["attributes"][LangfuseOtelSpanAttributes.EXPERIMENT_ID] + for span in root_spans + } + + assert len(experiment_ids) == 1 + assert result.experiment_id == next(iter(experiment_ids)) + def test_experiment_attributes_propagate_with_dataset( self, langfuse_client, memory_exporter, monkeypatch ): """Test experiment attribute propagation with Langfuse dataset.""" - # Mock the async API to create dataset run items - async def mock_create_dataset_run_item(*args, **kwargs): + # Mock the sync API used by run_experiment to create dataset run items + def mock_create_dataset_run_item(*args, **kwargs): from langfuse.api import DatasetRunItem - request = kwargs.get("request") return DatasetRunItem( id="mock-run-item-id", dataset_run_id="mock-dataset-run-id-123", - dataset_item_id=request.datasetItemId if request else "mock-item-id", + dataset_run_name=kwargs.get("run_name", "Dataset Test"), + dataset_item_id=kwargs.get("dataset_item_id", "mock-item-id"), trace_id="mock-trace-id", + observation_id=kwargs.get("observation_id"), + created_at=datetime.now(), + updated_at=datetime.now(), ) monkeypatch.setattr( - langfuse_client.async_api.dataset_run_items, + langfuse_client.api.dataset_run_items, "create", mock_create_dataset_run_item, ) @@ -2548,7 +2579,7 @@ def task_with_children(*, item, **kwargs): # Run experiment experiment_metadata = {"dataset_version": "v2", "test_run": "true"} - dataset.run_experiment( + result = dataset.run_experiment( name="Dataset Test", description="Dataset experiment description", task=task_with_children, @@ -2562,6 +2593,7 @@ def task_with_children(*, item, **kwargs): root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") assert len(root_spans) >= 1, "Should have at least 1 root span" first_root = root_spans[0] + assert result.experiment_id == "mock-dataset-run-id-123" # Root-only attributes should be on root self.verify_span_attribute( @@ -2588,6 +2620,11 @@ def task_with_children(*, item, **kwargs): LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID, dataset_item_id, ) + self.verify_span_attribute( + first_root, + LangfuseOtelSpanAttributes.EXPERIMENT_ID, + result.experiment_id, + ) # Should have experiment metadata self.verify_span_attribute( From de09c327e57d467b2944bdff0f0aba6a3828b7e6 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 1 Apr 2026 11:05:44 +0000 Subject: [PATCH 237/296] chore: release v4.0.5 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3240a1764..5e5068b99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.0.4" +version = "4.0.5" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 938561497..1288abbb3 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-03-23T20:02:18.050568Z" +exclude-newer = "2026-03-25T11:05:40.566191695Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.0.4" +version = "4.0.5" source = { editable = "." } dependencies = [ { name = "backoff" }, From b68013650ed280f878db820342e79a9cf86ed3bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:23:22 +0200 Subject: [PATCH 238/296] chore(deps): bump langchain-core from 1.2.21 to 1.2.22 (#1601) Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 1.2.21 to 1.2.22. - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.21...langchain-core==1.2.22) --- updated-dependencies: - dependency-name: langchain-core dependency-version: 1.2.22 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 1288abbb3..4e5cc9d2b 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-03-25T11:05:40.566191695Z" +exclude-newer = "2026-03-25T11:18:15.811969906Z" exclude-newer-span = "P7D" [[package]] @@ -521,7 +521,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.21" +version = "1.2.22" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -533,9 +533,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/e4/135ef5bbb5b97bdf15f777b86d7fba2ef8a162723ae96b3c7c1add9891a9/langchain_core-1.2.21.tar.gz", hash = "sha256:1a121d13976dc0908d5a8222262810ea483a4cf2b05006bdba75df5b11b554b3", size = 841063, upload-time = "2026-03-23T18:01:01.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487, upload-time = "2026-03-24T18:48:44.9Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/ae/f7591e57d4c6b64b521f9832fc471070902718c59bf135401f3b90a3f7ef/langchain_core-1.2.21-py3-none-any.whl", hash = "sha256:486cb405e2ecb0c407cb5fb5379ed0f919eb4b8a868b60cc8c3c15a3dfb560a7", size = 505756, upload-time = "2026-03-23T18:01:00.176Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829, upload-time = "2026-03-24T18:48:43.286Z" }, ] [[package]] From 6f9eaf20c86152dbd409cbf12ef80de4a27c38fb Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:59:55 +0200 Subject: [PATCH 239/296] feat(langchain): mark LangChain root observations in metadata (#1604) --- langfuse/langchain/CallbackHandler.py | 47 +++++++++++++++++++++++---- tests/test_langchain.py | 9 +++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 5b5dfe691..8d2c8db90 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -303,6 +303,28 @@ def _parse_langfuse_trace_attributes( return attributes + def _get_langchain_observation_metadata( + self, + *, + parent_run_id: Optional[UUID], + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + keep_langfuse_trace_attributes: bool = False, + ) -> Optional[Dict[str, Any]]: + observation_metadata = self.__join_tags_and_metadata( + tags=tags, + metadata=metadata, + keep_langfuse_trace_attributes=keep_langfuse_trace_attributes, + ) + + if parent_run_id is not None: + return observation_metadata + + root_metadata = observation_metadata.copy() if observation_metadata else {} + root_metadata["is_langchain_root"] = True + + return root_metadata + def on_chain_start( self, serialized: Optional[Dict[str, Any]], @@ -325,7 +347,11 @@ def on_chain_start( ) span_name = self.get_langchain_run_name(serialized, **kwargs) - span_metadata = self.__join_tags_and_metadata(tags, metadata) + span_metadata = self._get_langchain_observation_metadata( + parent_run_id=parent_run_id, + tags=tags, + metadata=metadata, + ) span_level = "DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None observation_type = self._get_observation_type_from_serialized( @@ -690,7 +716,11 @@ def on_tool_start( "on_tool_start", run_id, parent_run_id, input_str=input_str ) - meta = self.__join_tags_and_metadata(tags, metadata) + meta = self._get_langchain_observation_metadata( + parent_run_id=parent_run_id, + tags=tags, + metadata=metadata, + ) if not meta: meta = {} @@ -734,7 +764,11 @@ def on_retriever_start( "on_retriever_start", run_id, parent_run_id, query=query ) span_name = self.get_langchain_run_name(serialized, **kwargs) - span_metadata = self.__join_tags_and_metadata(tags, metadata) + span_metadata = self._get_langchain_observation_metadata( + parent_run_id=parent_run_id, + tags=tags, + metadata=metadata, + ) span_level = "DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None observation_type = self._get_observation_type_from_serialized( @@ -865,9 +899,10 @@ def __on_llm_action( content = { "name": self.get_langchain_run_name(serialized, **kwargs), "input": prompts, - "metadata": self.__join_tags_and_metadata( - tags, - metadata, + "metadata": self._get_langchain_observation_metadata( + parent_run_id=parent_run_id, + tags=tags, + metadata=metadata, # If llm is run isolated and outside chain, keep trace attributes keep_langfuse_trace_attributes=True if parent_run_id is None diff --git a/tests/test_langchain.py b/tests/test_langchain.py index 6c9d3eb4d..fa2bcfddb 100644 --- a/tests/test_langchain.py +++ b/tests/test_langchain.py @@ -67,6 +67,7 @@ def test_callback_generated_from_trace_chat(): assert langchain_generation_span.input != "" assert langchain_generation_span.output is not None assert langchain_generation_span.output != "" + assert langchain_generation_span.metadata["is_langchain_root"] is True def test_callback_generated_from_lcel_chain(): @@ -103,6 +104,11 @@ def test_callback_generated_from_lcel_chain(): trace.observations, ) )[0] + langchain_root_spans = [ + observation + for observation in trace.observations + if observation.metadata and observation.metadata.get("is_langchain_root") + ] assert langchain_generation_span.usage_details["input"] > 1 assert langchain_generation_span.usage_details["output"] > 0 @@ -111,6 +117,9 @@ def test_callback_generated_from_lcel_chain(): assert langchain_generation_span.input != "" assert langchain_generation_span.output is not None assert langchain_generation_span.output != "" + assert len(langchain_root_spans) == 1 + assert langchain_root_spans[0].type == "CHAIN" + assert langchain_root_spans[0].metadata["is_langchain_root"] is True @pytest.mark.skip(reason="Flaky") From 3e530af91f5f4df62ebe6025900c11ef38663303 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 1 Apr 2026 20:04:12 +0000 Subject: [PATCH 240/296] chore: release v4.0.6 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5e5068b99..7c2d2e497 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.0.5" +version = "4.0.6" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 4e5cc9d2b..611044a4b 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-03-25T11:18:15.811969906Z" +exclude-newer = "2026-03-25T20:04:08.521428164Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.0.5" +version = "4.0.6" source = { editable = "." } dependencies = [ { name = "backoff" }, From 95ff4f81e47582a9f188c71ede170cf33b49f550 Mon Sep 17 00:00:00 2001 From: Nimar Date: Wed, 8 Apr 2026 19:50:36 +0200 Subject: [PATCH 241/296] chore(ci): move to blacksmith runners (#1613) --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d81ae3f99..96c602f2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ concurrency: jobs: linting: - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install uv and set Python version @@ -31,7 +31,7 @@ jobs: run: uv run --frozen ruff check . type-checking: - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install uv and set Python version @@ -53,7 +53,7 @@ jobs: run: uv run --frozen mypy langfuse --no-error-summary ci: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 timeout-minutes: 30 env: LANGFUSE_BASE_URL: "http://localhost:3000" @@ -115,7 +115,7 @@ jobs: echo "::group::Seed db" cp .env.dev.example .env - pnpm run db:migrate + pnpm run db:migrate pnpm run db:seed echo "::endgroup::" rm -rf .env @@ -174,7 +174,7 @@ jobs: all-tests-passed: # This allows us to have a branch protection rule for tests and deploys with matrix - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2404 needs: [ci, linting, type-checking] if: always() steps: From b18a59e568fd604852fc9022d47a10242039b1b1 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 9 Apr 2026 16:38:58 +0200 Subject: [PATCH 242/296] feat(api): update API spec from langfuse/langfuse 1999706 (#1615) * feat(api): update API spec from langfuse/langfuse 1999706 * feat(scores): add TEXT type to score overloads and docstrings Extend string-value overloads in create_score, score_current_span, score_current_trace, score, and score_trace to accept TEXT alongside CATEGORICAL. Update all related docstrings. Add ExperimentScoreType to exclude TEXT from experiments/evals. Add integration test for TEXT scores. Co-Authored-By: Claude Opus 4.6 (1M context) * docs: simplify openapi spec update instructions in CONTRIBUTING.md The spec update is now automated via PR from the langfuse repo. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(scores): update stale casts and export ExperimentScoreType Update cast(Literal["CATEGORICAL"], ...) to include "TEXT" in score/score_trace/score_current_span/score_current_trace impl bodies. Add ExperimentScoreType to __all__ in types.py. Co-Authored-By: Claude Opus 4.6 (1M context) * ci: fix tests by adding dynamic retry * tests: adapt to update server logic * ci: cache pnpm store instead of node_modules to avoid stale server state Caching node_modules directly caused postinstall hooks (e.g. prisma generate) to be skipped, leading to stale generated artifacts that didn't match the current langfuse-server schema. Cache the pnpm store instead so installs are still fast but hooks run properly. Co-Authored-By: Claude Opus 4.6 (1M context) * ci: revert caching changes --------- Co-authored-by: langfuse-bot Co-authored-by: Tobias Wochinger Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 6 +- CONTRIBUTING.md | 4 +- langfuse/_client/client.py | 22 +++---- langfuse/_client/span.py | 16 ++--- langfuse/api/__init__.py | 18 ++++++ langfuse/api/commons/__init__.py | 12 ++++ langfuse/api/commons/types/__init__.py | 19 +++++- langfuse/api/commons/types/score.py | 49 ++++++++++++++- .../commons/types/score_config_data_type.py | 4 ++ langfuse/api/commons/types/score_data_type.py | 4 ++ langfuse/api/commons/types/score_v1.py | 39 +++++++++++- langfuse/api/commons/types/text_score.py | 21 +++++++ langfuse/api/commons/types/text_score_v1.py | 21 +++++++ langfuse/api/ingestion/types/score_body.py | 2 +- langfuse/api/legacy/score_v1/client.py | 4 +- langfuse/api/legacy/score_v1/raw_client.py | 4 +- .../score_v1/types/create_score_request.py | 2 +- langfuse/api/scores/__init__.py | 6 ++ langfuse/api/scores/types/__init__.py | 6 ++ .../scores/types/get_scores_response_data.py | 47 ++++++++++++++ .../types/get_scores_response_data_text.py | 15 +++++ langfuse/api/trace/client.py | 24 +++++-- langfuse/api/trace/raw_client.py | 24 ++++++- langfuse/experiment.py | 5 +- langfuse/types.py | 6 +- pyproject.toml | 1 + tests/test_core_sdk.py | 62 +++++++++++++++++++ tests/test_datasets.py | 21 +++---- uv.lock | 4 +- 29 files changed, 413 insertions(+), 55 deletions(-) create mode 100644 langfuse/api/commons/types/text_score.py create mode 100644 langfuse/api/commons/types/text_score_v1.py create mode 100644 langfuse/api/scores/types/get_scores_response_data_text.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96c602f2c..a95ec5e38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,9 +93,9 @@ jobs: uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: ./langfuse-server/node_modules - key: | - langfuse-server-${{ hashFiles('./langfuse-server/package-lock.json') }} - langfuse-server- + key: langfuse-serve-${{ hashFiles('langfuse-server/pnpm-lock.yaml') }} + restore-keys: | + langfuse-serve- - name: Run langfuse server run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53fede752..946400a5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,9 +45,7 @@ uv run mypy langfuse --no-error-summary ### Update openapi spec -1. Generate Fern Python SDK in [langfuse](https://github.com/langfuse/langfuse) and copy the files generated in `generated/python` into the `langfuse/api` folder in this repo. -2. Execute the linter by running `uv run ruff format .` -3. Rebuild and deploy the package to PyPi. +A PR with the changes is automatically created upon changing the Spec in the langfuse repo. ### Publish release diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 85ec83a4e..04d8fae2c 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -1747,7 +1747,7 @@ def create_score( trace_id: Optional[str] = None, score_id: Optional[str] = None, observation_id: Optional[str] = None, - data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", + data_type: Optional[Literal["CATEGORICAL", "TEXT"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, @@ -1777,13 +1777,13 @@ def create_score( Args: name: Name of the score (e.g., "relevance", "accuracy") - value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL) + value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT) session_id: ID of the Langfuse session to associate the score with dataset_run_id: ID of the Langfuse dataset run to associate the score with trace_id: ID of the Langfuse trace to associate the score with observation_id: Optional ID of the specific observation to score. Trace ID must be provided too. score_id: Optional custom ID for the score (auto-generated if not provided) - data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) + data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, or TEXT) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse metadata: Optional metadata to be attached to the score @@ -1907,7 +1907,7 @@ def score_current_span( name: str, value: str, score_id: Optional[str] = None, - data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", + data_type: Optional[Literal["CATEGORICAL", "TEXT"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, @@ -1931,9 +1931,9 @@ def score_current_span( Args: name: Name of the score (e.g., "relevance", "accuracy") - value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL) + value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT) score_id: Optional custom ID for the score (auto-generated if not provided) - data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) + data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, or TEXT) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse metadata: Optional metadata to be attached to the score @@ -1971,7 +1971,7 @@ def score_current_span( name=name, value=cast(str, value), score_id=score_id, - data_type=cast(Literal["CATEGORICAL"], data_type), + data_type=cast(Literal["CATEGORICAL", "TEXT"], data_type), comment=comment, config_id=config_id, metadata=metadata, @@ -1997,7 +1997,7 @@ def score_current_trace( name: str, value: str, score_id: Optional[str] = None, - data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL", + data_type: Optional[Literal["CATEGORICAL", "TEXT"]] = "CATEGORICAL", comment: Optional[str] = None, config_id: Optional[str] = None, metadata: Optional[Any] = None, @@ -2022,9 +2022,9 @@ def score_current_trace( Args: name: Name of the score (e.g., "user_satisfaction", "overall_quality") - value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL) + value: Score value (can be numeric for NUMERIC/BOOLEAN types or string for CATEGORICAL/TEXT) score_id: Optional custom ID for the score (auto-generated if not provided) - data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) + data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, or TEXT) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse metadata: Optional metadata to be attached to the score @@ -2060,7 +2060,7 @@ def score_current_trace( name=name, value=cast(str, value), score_id=score_id, - data_type=cast(Literal["CATEGORICAL"], data_type), + data_type=cast(Literal["CATEGORICAL", "TEXT"], data_type), comment=comment, config_id=config_id, metadata=metadata, diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 2590262ce..bd0c638a7 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -308,7 +308,7 @@ def score( value: str, score_id: Optional[str] = None, data_type: Optional[ - Literal[ScoreDataType.CATEGORICAL] + Literal[ScoreDataType.CATEGORICAL, ScoreDataType.TEXT] ] = ScoreDataType.CATEGORICAL, comment: Optional[str] = None, config_id: Optional[str] = None, @@ -335,9 +335,9 @@ def score( Args: name: Name of the score (e.g., "relevance", "accuracy") - value: Score value (numeric for NUMERIC/BOOLEAN, string for CATEGORICAL) + value: Score value (numeric for NUMERIC/BOOLEAN, string for CATEGORICAL/TEXT) score_id: Optional custom ID for the score (auto-generated if not provided) - data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) + data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, or TEXT) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse timestamp: Optional timestamp for the score (defaults to current UTC time) @@ -364,7 +364,7 @@ def score( trace_id=self.trace_id, observation_id=self.id, score_id=score_id, - data_type=cast(Literal["CATEGORICAL"], data_type), + data_type=cast(Literal["CATEGORICAL", "TEXT"], data_type), comment=comment, config_id=config_id, timestamp=timestamp, @@ -395,7 +395,7 @@ def score_trace( value: str, score_id: Optional[str] = None, data_type: Optional[ - Literal[ScoreDataType.CATEGORICAL] + Literal[ScoreDataType.CATEGORICAL, ScoreDataType.TEXT] ] = ScoreDataType.CATEGORICAL, comment: Optional[str] = None, config_id: Optional[str] = None, @@ -423,9 +423,9 @@ def score_trace( Args: name: Name of the score (e.g., "user_satisfaction", "overall_quality") - value: Score value (numeric for NUMERIC/BOOLEAN, string for CATEGORICAL) + value: Score value (numeric for NUMERIC/BOOLEAN, string for CATEGORICAL/TEXT) score_id: Optional custom ID for the score (auto-generated if not provided) - data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL) + data_type: Type of score (NUMERIC, BOOLEAN, CATEGORICAL, or TEXT) comment: Optional comment or explanation for the score config_id: Optional ID of a score config defined in Langfuse timestamp: Optional timestamp for the score (defaults to current UTC time) @@ -451,7 +451,7 @@ def score_trace( value=cast(str, value), trace_id=self.trace_id, score_id=score_id, - data_type=cast(Literal["CATEGORICAL"], data_type), + data_type=cast(Literal["CATEGORICAL", "TEXT"], data_type), comment=comment, config_id=config_id, timestamp=timestamp, diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 443f5cdd2..aa103cf12 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -112,12 +112,16 @@ ScoreV1_Boolean, ScoreV1_Categorical, ScoreV1_Numeric, + ScoreV1_Text, Score_Boolean, Score_Categorical, Score_Correction, Score_Numeric, + Score_Text, Session, SessionWithTraces, + TextScore, + TextScoreV1, Trace, TraceWithDetails, TraceWithFullDetails, @@ -281,10 +285,12 @@ GetScoresResponseDataCategorical, GetScoresResponseDataCorrection, GetScoresResponseDataNumeric, + GetScoresResponseDataText, GetScoresResponseData_Boolean, GetScoresResponseData_Categorical, GetScoresResponseData_Correction, GetScoresResponseData_Numeric, + GetScoresResponseData_Text, GetScoresResponseTraceData, ) from .sessions import PaginatedSessions @@ -377,10 +383,12 @@ "GetScoresResponseDataCategorical": ".scores", "GetScoresResponseDataCorrection": ".scores", "GetScoresResponseDataNumeric": ".scores", + "GetScoresResponseDataText": ".scores", "GetScoresResponseData_Boolean": ".scores", "GetScoresResponseData_Categorical": ".scores", "GetScoresResponseData_Correction": ".scores", "GetScoresResponseData_Numeric": ".scores", + "GetScoresResponseData_Text": ".scores", "GetScoresResponseTraceData": ".scores", "HealthResponse": ".health", "IngestionError": ".ingestion", @@ -489,10 +497,12 @@ "ScoreV1_Boolean": ".commons", "ScoreV1_Categorical": ".commons", "ScoreV1_Numeric": ".commons", + "ScoreV1_Text": ".commons", "Score_Boolean": ".commons", "Score_Categorical": ".commons", "Score_Correction": ".commons", "Score_Numeric": ".commons", + "Score_Text": ".commons", "SdkLogBody": ".ingestion", "SdkLogEvent": ".ingestion", "ServiceProviderConfig": ".scim", @@ -501,6 +511,8 @@ "SessionWithTraces": ".commons", "Sort": ".trace", "TextPrompt": ".prompts", + "TextScore": ".commons", + "TextScoreV1": ".commons", "Trace": ".commons", "TraceBody": ".ingestion", "TraceEvent": ".ingestion", @@ -664,10 +676,12 @@ def __dir__(): "GetScoresResponseDataCategorical", "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", + "GetScoresResponseDataText", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", + "GetScoresResponseData_Text", "GetScoresResponseTraceData", "HealthResponse", "IngestionError", @@ -776,10 +790,12 @@ def __dir__(): "ScoreV1_Boolean", "ScoreV1_Categorical", "ScoreV1_Numeric", + "ScoreV1_Text", "Score_Boolean", "Score_Categorical", "Score_Correction", "Score_Numeric", + "Score_Text", "SdkLogBody", "SdkLogEvent", "ServiceProviderConfig", @@ -788,6 +804,8 @@ def __dir__(): "SessionWithTraces", "Sort", "TextPrompt", + "TextScore", + "TextScoreV1", "Trace", "TraceBody", "TraceEvent", diff --git a/langfuse/api/commons/__init__.py b/langfuse/api/commons/__init__.py index af79e3e25..81cb57f96 100644 --- a/langfuse/api/commons/__init__.py +++ b/langfuse/api/commons/__init__.py @@ -47,12 +47,16 @@ ScoreV1_Boolean, ScoreV1_Categorical, ScoreV1_Numeric, + ScoreV1_Text, Score_Boolean, Score_Categorical, Score_Correction, Score_Numeric, + Score_Text, Session, SessionWithTraces, + TextScore, + TextScoreV1, Trace, TraceWithDetails, TraceWithFullDetails, @@ -110,12 +114,16 @@ "ScoreV1_Boolean": ".types", "ScoreV1_Categorical": ".types", "ScoreV1_Numeric": ".types", + "ScoreV1_Text": ".types", "Score_Boolean": ".types", "Score_Categorical": ".types", "Score_Correction": ".types", "Score_Numeric": ".types", + "Score_Text": ".types", "Session": ".types", "SessionWithTraces": ".types", + "TextScore": ".types", + "TextScoreV1": ".types", "Trace": ".types", "TraceWithDetails": ".types", "TraceWithFullDetails": ".types", @@ -196,12 +204,16 @@ def __dir__(): "ScoreV1_Boolean", "ScoreV1_Categorical", "ScoreV1_Numeric", + "ScoreV1_Text", "Score_Boolean", "Score_Categorical", "Score_Correction", "Score_Numeric", + "Score_Text", "Session", "SessionWithTraces", + "TextScore", + "TextScoreV1", "Trace", "TraceWithDetails", "TraceWithFullDetails", diff --git a/langfuse/api/commons/types/__init__.py b/langfuse/api/commons/types/__init__.py index b7834caf3..5ce0a58cd 100644 --- a/langfuse/api/commons/types/__init__.py +++ b/langfuse/api/commons/types/__init__.py @@ -43,14 +43,23 @@ Score_Categorical, Score_Correction, Score_Numeric, + Score_Text, ) from .score_config import ScoreConfig from .score_config_data_type import ScoreConfigDataType from .score_data_type import ScoreDataType from .score_source import ScoreSource - from .score_v1 import ScoreV1, ScoreV1_Boolean, ScoreV1_Categorical, ScoreV1_Numeric + from .score_v1 import ( + ScoreV1, + ScoreV1_Boolean, + ScoreV1_Categorical, + ScoreV1_Numeric, + ScoreV1_Text, + ) from .session import Session from .session_with_traces import SessionWithTraces + from .text_score import TextScore + from .text_score_v1 import TextScoreV1 from .trace import Trace from .trace_with_details import TraceWithDetails from .trace_with_full_details import TraceWithFullDetails @@ -96,12 +105,16 @@ "ScoreV1_Boolean": ".score_v1", "ScoreV1_Categorical": ".score_v1", "ScoreV1_Numeric": ".score_v1", + "ScoreV1_Text": ".score_v1", "Score_Boolean": ".score", "Score_Categorical": ".score", "Score_Correction": ".score", "Score_Numeric": ".score", + "Score_Text": ".score", "Session": ".session", "SessionWithTraces": ".session_with_traces", + "TextScore": ".text_score", + "TextScoreV1": ".text_score_v1", "Trace": ".trace", "TraceWithDetails": ".trace_with_details", "TraceWithFullDetails": ".trace_with_full_details", @@ -177,12 +190,16 @@ def __dir__(): "ScoreV1_Boolean", "ScoreV1_Categorical", "ScoreV1_Numeric", + "ScoreV1_Text", "Score_Boolean", "Score_Categorical", "Score_Correction", "Score_Numeric", + "Score_Text", "Session", "SessionWithTraces", + "TextScore", + "TextScoreV1", "Trace", "TraceWithDetails", "TraceWithFullDetails", diff --git a/langfuse/api/commons/types/score.py b/langfuse/api/commons/types/score.py index bfcb94f3d..33c901c5f 100644 --- a/langfuse/api/commons/types/score.py +++ b/langfuse/api/commons/types/score.py @@ -195,7 +195,54 @@ class Score_Correction(UniversalBaseModel): ) +class Score_Text(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["TEXT"], FieldMetadata(alias="dataType") + ] = "TEXT" + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + Score = typing_extensions.Annotated[ - typing.Union[Score_Numeric, Score_Categorical, Score_Boolean, Score_Correction], + typing.Union[ + Score_Numeric, Score_Categorical, Score_Boolean, Score_Correction, Score_Text + ], pydantic.Field(discriminator="data_type"), ] diff --git a/langfuse/api/commons/types/score_config_data_type.py b/langfuse/api/commons/types/score_config_data_type.py index 4f4660305..683b46a0f 100644 --- a/langfuse/api/commons/types/score_config_data_type.py +++ b/langfuse/api/commons/types/score_config_data_type.py @@ -11,12 +11,14 @@ class ScoreConfigDataType(enum.StrEnum): NUMERIC = "NUMERIC" BOOLEAN = "BOOLEAN" CATEGORICAL = "CATEGORICAL" + TEXT = "TEXT" def visit( self, numeric: typing.Callable[[], T_Result], boolean: typing.Callable[[], T_Result], categorical: typing.Callable[[], T_Result], + text: typing.Callable[[], T_Result], ) -> T_Result: if self is ScoreConfigDataType.NUMERIC: return numeric() @@ -24,3 +26,5 @@ def visit( return boolean() if self is ScoreConfigDataType.CATEGORICAL: return categorical() + if self is ScoreConfigDataType.TEXT: + return text() diff --git a/langfuse/api/commons/types/score_data_type.py b/langfuse/api/commons/types/score_data_type.py index c29a77f07..18301b51f 100644 --- a/langfuse/api/commons/types/score_data_type.py +++ b/langfuse/api/commons/types/score_data_type.py @@ -12,6 +12,7 @@ class ScoreDataType(enum.StrEnum): BOOLEAN = "BOOLEAN" CATEGORICAL = "CATEGORICAL" CORRECTION = "CORRECTION" + TEXT = "TEXT" def visit( self, @@ -19,6 +20,7 @@ def visit( boolean: typing.Callable[[], T_Result], categorical: typing.Callable[[], T_Result], correction: typing.Callable[[], T_Result], + text: typing.Callable[[], T_Result], ) -> T_Result: if self is ScoreDataType.NUMERIC: return numeric() @@ -28,3 +30,5 @@ def visit( return categorical() if self is ScoreDataType.CORRECTION: return correction() + if self is ScoreDataType.TEXT: + return text() diff --git a/langfuse/api/commons/types/score_v1.py b/langfuse/api/commons/types/score_v1.py index 1a409ce2f..e17e61b89 100644 --- a/langfuse/api/commons/types/score_v1.py +++ b/langfuse/api/commons/types/score_v1.py @@ -125,7 +125,44 @@ class ScoreV1_Boolean(UniversalBaseModel): ) +class ScoreV1_Text(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["TEXT"], FieldMetadata(alias="dataType") + ] = "TEXT" + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + name: str + source: ScoreSource + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + ScoreV1 = typing_extensions.Annotated[ - typing.Union[ScoreV1_Numeric, ScoreV1_Categorical, ScoreV1_Boolean], + typing.Union[ScoreV1_Numeric, ScoreV1_Categorical, ScoreV1_Boolean, ScoreV1_Text], pydantic.Field(discriminator="data_type"), ] diff --git a/langfuse/api/commons/types/text_score.py b/langfuse/api/commons/types/text_score.py new file mode 100644 index 000000000..438e30f47 --- /dev/null +++ b/langfuse/api/commons/types/text_score.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score import BaseScore + + +class TextScore(BaseScore): + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The text content of the score (1-500 characters) + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/commons/types/text_score_v1.py b/langfuse/api/commons/types/text_score_v1.py new file mode 100644 index 000000000..937ca281e --- /dev/null +++ b/langfuse/api/commons/types/text_score_v1.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .base_score_v1 import BaseScoreV1 + + +class TextScoreV1(BaseScoreV1): + string_value: typing_extensions.Annotated[ + str, FieldMetadata(alias="stringValue") + ] = pydantic.Field() + """ + The text content of the score (1-500 characters) + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/ingestion/types/score_body.py b/langfuse/api/ingestion/types/score_body.py index f559187b6..d9a32b6a7 100644 --- a/langfuse/api/ingestion/types/score_body.py +++ b/langfuse/api/ingestion/types/score_body.py @@ -51,7 +51,7 @@ class ScoreBody(UniversalBaseModel): value: CreateScoreValue = pydantic.Field() """ - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + The value of the score. Must be passed as string for categorical and text scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false). Text score values must be between 1 and 500 characters. """ comment: typing.Optional[str] = None diff --git a/langfuse/api/legacy/score_v1/client.py b/langfuse/api/legacy/score_v1/client.py index 7c7a214e3..03ca8b836 100644 --- a/langfuse/api/legacy/score_v1/client.py +++ b/langfuse/api/legacy/score_v1/client.py @@ -54,7 +54,7 @@ def create( name : str value : CreateScoreValue - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + The value of the score. Must be passed as string for categorical and text scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false). Text score values must be between 1 and 500 characters. id : typing.Optional[str] @@ -203,7 +203,7 @@ async def create( name : str value : CreateScoreValue - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + The value of the score. Must be passed as string for categorical and text scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false). Text score values must be between 1 and 500 characters. id : typing.Optional[str] diff --git a/langfuse/api/legacy/score_v1/raw_client.py b/langfuse/api/legacy/score_v1/raw_client.py index 9bcbe082d..834560ec9 100644 --- a/langfuse/api/legacy/score_v1/raw_client.py +++ b/langfuse/api/legacy/score_v1/raw_client.py @@ -53,7 +53,7 @@ def create( name : str value : CreateScoreValue - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + The value of the score. Must be passed as string for categorical and text scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false). Text score values must be between 1 and 500 characters. id : typing.Optional[str] @@ -314,7 +314,7 @@ async def create( name : str value : CreateScoreValue - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + The value of the score. Must be passed as string for categorical and text scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false). Text score values must be between 1 and 500 characters. id : typing.Optional[str] diff --git a/langfuse/api/legacy/score_v1/types/create_score_request.py b/langfuse/api/legacy/score_v1/types/create_score_request.py index d54333ac3..a0397bdfc 100644 --- a/langfuse/api/legacy/score_v1/types/create_score_request.py +++ b/langfuse/api/legacy/score_v1/types/create_score_request.py @@ -39,7 +39,7 @@ class CreateScoreRequest(UniversalBaseModel): name: str value: CreateScoreValue = pydantic.Field() """ - The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) + The value of the score. Must be passed as string for categorical and text scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false). Text score values must be between 1 and 500 characters. """ comment: typing.Optional[str] = None diff --git a/langfuse/api/scores/__init__.py b/langfuse/api/scores/__init__.py index d320aecfc..d57fb5371 100644 --- a/langfuse/api/scores/__init__.py +++ b/langfuse/api/scores/__init__.py @@ -13,10 +13,12 @@ GetScoresResponseDataCategorical, GetScoresResponseDataCorrection, GetScoresResponseDataNumeric, + GetScoresResponseDataText, GetScoresResponseData_Boolean, GetScoresResponseData_Categorical, GetScoresResponseData_Correction, GetScoresResponseData_Numeric, + GetScoresResponseData_Text, GetScoresResponseTraceData, ) _dynamic_imports: typing.Dict[str, str] = { @@ -26,10 +28,12 @@ "GetScoresResponseDataCategorical": ".types", "GetScoresResponseDataCorrection": ".types", "GetScoresResponseDataNumeric": ".types", + "GetScoresResponseDataText": ".types", "GetScoresResponseData_Boolean": ".types", "GetScoresResponseData_Categorical": ".types", "GetScoresResponseData_Correction": ".types", "GetScoresResponseData_Numeric": ".types", + "GetScoresResponseData_Text": ".types", "GetScoresResponseTraceData": ".types", } @@ -68,9 +72,11 @@ def __dir__(): "GetScoresResponseDataCategorical", "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", + "GetScoresResponseDataText", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", + "GetScoresResponseData_Text", "GetScoresResponseTraceData", ] diff --git a/langfuse/api/scores/types/__init__.py b/langfuse/api/scores/types/__init__.py index 3ee0246d4..5b82ed448 100644 --- a/langfuse/api/scores/types/__init__.py +++ b/langfuse/api/scores/types/__init__.py @@ -13,11 +13,13 @@ GetScoresResponseData_Categorical, GetScoresResponseData_Correction, GetScoresResponseData_Numeric, + GetScoresResponseData_Text, ) from .get_scores_response_data_boolean import GetScoresResponseDataBoolean from .get_scores_response_data_categorical import GetScoresResponseDataCategorical from .get_scores_response_data_correction import GetScoresResponseDataCorrection from .get_scores_response_data_numeric import GetScoresResponseDataNumeric + from .get_scores_response_data_text import GetScoresResponseDataText from .get_scores_response_trace_data import GetScoresResponseTraceData _dynamic_imports: typing.Dict[str, str] = { "GetScoresResponse": ".get_scores_response", @@ -26,10 +28,12 @@ "GetScoresResponseDataCategorical": ".get_scores_response_data_categorical", "GetScoresResponseDataCorrection": ".get_scores_response_data_correction", "GetScoresResponseDataNumeric": ".get_scores_response_data_numeric", + "GetScoresResponseDataText": ".get_scores_response_data_text", "GetScoresResponseData_Boolean": ".get_scores_response_data", "GetScoresResponseData_Categorical": ".get_scores_response_data", "GetScoresResponseData_Correction": ".get_scores_response_data", "GetScoresResponseData_Numeric": ".get_scores_response_data", + "GetScoresResponseData_Text": ".get_scores_response_data", "GetScoresResponseTraceData": ".get_scores_response_trace_data", } @@ -68,9 +72,11 @@ def __dir__(): "GetScoresResponseDataCategorical", "GetScoresResponseDataCorrection", "GetScoresResponseDataNumeric", + "GetScoresResponseDataText", "GetScoresResponseData_Boolean", "GetScoresResponseData_Categorical", "GetScoresResponseData_Correction", "GetScoresResponseData_Numeric", + "GetScoresResponseData_Text", "GetScoresResponseTraceData", ] diff --git a/langfuse/api/scores/types/get_scores_response_data.py b/langfuse/api/scores/types/get_scores_response_data.py index d1cdda417..f85cd471c 100644 --- a/langfuse/api/scores/types/get_scores_response_data.py +++ b/langfuse/api/scores/types/get_scores_response_data.py @@ -200,12 +200,59 @@ class GetScoresResponseData_Correction(UniversalBaseModel): ) +class GetScoresResponseData_Text(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["TEXT"], FieldMetadata(alias="dataType") + ] = "TEXT" + trace: typing.Optional[GetScoresResponseTraceData] = None + string_value: typing_extensions.Annotated[str, FieldMetadata(alias="stringValue")] + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + session_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="sessionId") + ] = None + observation_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="observationId") + ] = None + dataset_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="datasetRunId") + ] = None + name: str + source: ScoreSource + timestamp: dt.datetime + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + comment: typing.Optional[str] = None + metadata: typing.Any + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + environment: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + GetScoresResponseData = typing_extensions.Annotated[ typing.Union[ GetScoresResponseData_Numeric, GetScoresResponseData_Categorical, GetScoresResponseData_Boolean, GetScoresResponseData_Correction, + GetScoresResponseData_Text, ], pydantic.Field(discriminator="data_type"), ] diff --git a/langfuse/api/scores/types/get_scores_response_data_text.py b/langfuse/api/scores/types/get_scores_response_data_text.py new file mode 100644 index 000000000..4949afe74 --- /dev/null +++ b/langfuse/api/scores/types/get_scores_response_data_text.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...commons.types.text_score import TextScore +from .get_scores_response_trace_data import GetScoresResponseTraceData + + +class GetScoresResponseDataText(TextScore): + trace: typing.Optional[GetScoresResponseTraceData] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/trace/client.py b/langfuse/api/trace/client.py index e5dcbb33c..d3d7b760a 100644 --- a/langfuse/api/trace/client.py +++ b/langfuse/api/trace/client.py @@ -30,7 +30,11 @@ def with_raw_response(self) -> RawTraceClient: return self._raw_client def get( - self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + self, + trace_id: str, + *, + fields: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> TraceWithFullDetails: """ Get a specific trace @@ -40,6 +44,9 @@ def get( trace_id : str The unique langfuse identifier of a trace + fields : typing.Optional[str] + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -63,7 +70,9 @@ def get( trace_id="traceId", ) """ - _response = self._raw_client.get(trace_id, request_options=request_options) + _response = self._raw_client.get( + trace_id, fields=fields, request_options=request_options + ) return _response.data def delete( @@ -369,7 +378,11 @@ def with_raw_response(self) -> AsyncRawTraceClient: return self._raw_client async def get( - self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + self, + trace_id: str, + *, + fields: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> TraceWithFullDetails: """ Get a specific trace @@ -379,6 +392,9 @@ async def get( trace_id : str The unique langfuse identifier of a trace + fields : typing.Optional[str] + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -411,7 +427,7 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._raw_client.get( - trace_id, request_options=request_options + trace_id, fields=fields, request_options=request_options ) return _response.data diff --git a/langfuse/api/trace/raw_client.py b/langfuse/api/trace/raw_client.py index 10f3d15ec..9e3d47c68 100644 --- a/langfuse/api/trace/raw_client.py +++ b/langfuse/api/trace/raw_client.py @@ -29,7 +29,11 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper def get( - self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + self, + trace_id: str, + *, + fields: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TraceWithFullDetails]: """ Get a specific trace @@ -39,6 +43,9 @@ def get( trace_id : str The unique langfuse identifier of a trace + fields : typing.Optional[str] + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -49,6 +56,9 @@ def get( _response = self._client_wrapper.httpx_client.request( f"api/public/traces/{jsonable_encoder(trace_id)}", method="GET", + params={ + "fields": fields, + }, request_options=request_options, ) try: @@ -621,7 +631,11 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper async def get( - self, trace_id: str, *, request_options: typing.Optional[RequestOptions] = None + self, + trace_id: str, + *, + fields: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TraceWithFullDetails]: """ Get a specific trace @@ -631,6 +645,9 @@ async def get( trace_id : str The unique langfuse identifier of a trace + fields : typing.Optional[str] + Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -641,6 +658,9 @@ async def get( _response = await self._client_wrapper.httpx_client.request( f"api/public/traces/{jsonable_encoder(trace_id)}", method="GET", + params={ + "fields": fields, + }, request_options=request_options, ) try: diff --git a/langfuse/experiment.py b/langfuse/experiment.py index 6e4b32e10..67b50a900 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -17,8 +17,9 @@ Union, ) -from langfuse.api import DatasetItem, ScoreDataType +from langfuse.api import DatasetItem from langfuse.logger import langfuse_logger as logger +from langfuse.types import ExperimentScoreType class LocalExperimentItem(TypedDict, total=False): @@ -184,7 +185,7 @@ def __init__( value: Union[int, float, str, bool], comment: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, - data_type: Optional[ScoreDataType] = None, + data_type: Optional[ExperimentScoreType] = None, config_id: Optional[str] = None, ): """Initialize an Evaluation with the provided data. diff --git a/langfuse/types.py b/langfuse/types.py index 067088e40..c3029e713 100644 --- a/langfuse/types.py +++ b/langfuse/types.py @@ -35,7 +35,10 @@ def my_evaluator(*, output: str, **kwargs) -> Evaluation: SpanLevel = Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"] -ScoreDataType = Literal["NUMERIC", "CATEGORICAL", "BOOLEAN"] +ScoreDataType = Literal["NUMERIC", "CATEGORICAL", "BOOLEAN", "TEXT"] + +# Text scores are not supported for evals and experiments +ExperimentScoreType = Literal["NUMERIC", "CATEGORICAL", "BOOLEAN"] class MaskFunction(Protocol): @@ -73,6 +76,7 @@ class TraceContext(TypedDict): __all__ = [ "SpanLevel", "ScoreDataType", + "ExperimentScoreType", "MaskFunction", "ParsedMediaReference", "TraceContext", diff --git a/pyproject.toml b/pyproject.toml index 7c2d2e497..267f52bcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dev = [ "langgraph>=1,<2", "autoevals>=0.0.130,<0.1", "opentelemetry-instrumentation-threading>=0.59b0,<1", + "tenacity>=9.1.4", ] docs = [ "pdoc>=15.0.4,<16", diff --git a/tests/test_core_sdk.py b/tests/test_core_sdk.py index 91064de23..b873e3206 100644 --- a/tests/test_core_sdk.py +++ b/tests/test_core_sdk.py @@ -5,6 +5,7 @@ from time import sleep import pytest +from tenacity import Retrying, stop_after_delay, wait_fixed from langfuse import Langfuse, propagate_attributes from langfuse._client.resource_manager import LangfuseResourceManager @@ -321,6 +322,67 @@ def test_create_categorical_score(): assert created_score["stringValue"] == "high score" +def test_create_text_score(): + langfuse = Langfuse() + api_wrapper = LangfuseAPI() + + # Create a span and set trace properties + with langfuse.start_as_current_observation(name="test-span") as span: + with propagate_attributes( + trace_name="this-is-so-great-new", + user_id="test", + metadata={"test": "test"}, + ): + # Get trace ID for later use + trace_id = span.trace_id + + # Ensure data is sent + langfuse.flush() + sleep(2) + + # Create a text score + score_id = create_uuid() + langfuse.create_score( + score_id=score_id, + trace_id=trace_id, + name="this-is-a-score", + value="This is a detailed text evaluation of the output quality.", + data_type="TEXT", + ) + + # Create a generation in the same trace + generation = langfuse.start_observation( + as_type="generation", + name="yet another child", + metadata="test", + trace_context={"trace_id": trace_id}, + ) + generation.end() + + # Ensure data is sent + langfuse.flush() + + # Retrieve and verify with retry + for attempt in Retrying( + stop=stop_after_delay(10), wait=wait_fixed(0.1), reraise=True + ): + with attempt: + trace = api_wrapper.get_trace(trace_id) + + # Find the score we created by name + created_score = next( + (s for s in trace["scores"] if s["name"] == "this-is-a-score"), None + ) + assert created_score is not None, "Score not found in trace" + assert created_score["id"] == score_id + assert created_score["dataType"] == "TEXT" + + assert ( + created_score["stringValue"] + == "This is a detailed text evaluation of the output quality." + ) + + def test_create_score_with_custom_timestamp(): langfuse = Langfuse() api_wrapper = LangfuseAPI() diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 7cbbed817..209a0d19f 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -120,18 +120,17 @@ def test_upsert_and_get_dataset_item(): ) # Refresh dataset and find updated item - dataset = langfuse.get_dataset(name) - get_new_item = None - for i in dataset.items: - if i.id == item.id: - get_new_item = i - break + archived_item = langfuse.api.dataset_items.get(id=item.id) + + assert archived_item is not None + assert archived_item.input == new_input + assert archived_item.id == item.id + assert archived_item.expected_output == new_input + assert archived_item.status == DatasetStatus.ARCHIVED - assert get_new_item is not None - assert get_new_item.input == new_input - assert get_new_item.id == item.id - assert get_new_item.expected_output == new_input - assert get_new_item.status == DatasetStatus.ARCHIVED + # List endpoint does not contain archived items + dataset = langfuse.get_dataset(name) + assert all(i.id != item.id for i in dataset.items) def test_run_experiment(): diff --git a/uv.lock b/uv.lock index 611044a4b..e5740bff8 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-03-25T20:04:08.521428164Z" +exclude-newer = "2026-03-31T15:26:41.189939Z" exclude-newer-span = "P7D" [[package]] @@ -583,6 +583,7 @@ dev = [ { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, + { name = "tenacity" }, ] docs = [ { name = "pdoc" }, @@ -616,6 +617,7 @@ dev = [ { name = "pytest-timeout", specifier = ">=2.1.0,<3" }, { name = "pytest-xdist", specifier = ">=3.3.1,<4" }, { name = "ruff", specifier = ">=0.15.2,<0.16" }, + { name = "tenacity", specifier = ">=9.1.4" }, ] docs = [{ name = "pdoc", specifier = ">=15.0.4,<16" }] From 01887ddf38cff2c90968e923384843e20fe4903a Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 9 Apr 2026 14:44:39 +0000 Subject: [PATCH 243/296] chore: release v4.1.0 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 267f52bcf..e6e97a6b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.0.6" +version = "4.1.0" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index e5740bff8..24b2877c6 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-03-31T15:26:41.189939Z" +exclude-newer = "2026-04-02T14:44:34.959905188Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.0.6" +version = "4.1.0" source = { editable = "." } dependencies = [ { name = "backoff" }, From fe09fd52b61423965032a591ee4a3ae46ec69485 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:54:25 +0200 Subject: [PATCH 244/296] feat(client): add custom span exporter support (#1618) --- langfuse/_client/client.py | 6 ++- langfuse/_client/get_client.py | 1 + langfuse/_client/resource_manager.py | 6 +++ langfuse/_client/span_processor.py | 52 +++++++++++++------------ tests/test_additional_headers_simple.py | 30 ++++++++++++++ tests/test_resource_manager.py | 28 +++++++++++++ 6 files changed, 97 insertions(+), 26 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 04d8fae2c..f0b469320 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -29,6 +29,7 @@ import httpx from opentelemetry import trace as otel_trace_api from opentelemetry.sdk.trace import ReadableSpan, TracerProvider +from opentelemetry.sdk.trace.export import SpanExporter from opentelemetry.sdk.trace.id_generator import RandomIdGenerator from opentelemetry.util._decorator import ( _AgnosticContextManager, @@ -179,8 +180,9 @@ class Langfuse: ) ``` should_export_span (Optional[Callable[[ReadableSpan], bool]]): Callback to decide whether to export a span. If omitted, Langfuse uses the default filter (Langfuse SDK spans, spans with `gen_ai.*` attributes, and known LLM instrumentation scopes). - additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. + additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and in the default OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. If `span_exporter` is provided, these headers are not wired into that exporter and must be configured on the exporter instance directly. tracer_provider(Optional[TracerProvider]): OpenTelemetry TracerProvider to use for Langfuse. This can be useful to set to have disconnected tracing between Langfuse and other OpenTelemetry-span emitting libraries. Note: To track active spans, the context is still shared between TracerProviders. This may lead to broken trace trees. + span_exporter (Optional[SpanExporter]): Custom OpenTelemetry span exporter for the Langfuse span processor. If omitted, Langfuse creates an OTLPSpanExporter pointed at the Langfuse OTLP endpoint. If provided, Langfuse does not wire `base_url`, exporter headers, exporter auth, or exporter timeout into it. Configure endpoint, headers, and timeout on the exporter instance directly. If you are sending spans to Langfuse v4 or using Langfuse Cloud Fast Preview, include `x-langfuse-ingestion-version=4` on the exporter to enable real time processing of exported spans. Example: ```python @@ -244,6 +246,7 @@ def __init__( should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, + span_exporter: Optional[SpanExporter] = None, ): self._base_url = ( base_url @@ -340,6 +343,7 @@ def __init__( should_export_span=should_export_span, additional_headers=additional_headers, tracer_provider=tracer_provider, + span_exporter=span_exporter, ) self._mask = self._resources.mask diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index dd2ee4a29..e2ebab0ed 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -54,6 +54,7 @@ def _create_client_from_instance( should_export_span=instance.should_export_span, additional_headers=instance.additional_headers, tracer_provider=instance.tracer_provider, + span_exporter=instance.span_exporter, httpx_client=instance.httpx_client, ) diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 55f23a782..6e277e0db 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -24,6 +24,7 @@ from opentelemetry import trace as otel_trace_api from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import ReadableSpan, TracerProvider +from opentelemetry.sdk.trace.export import SpanExporter from opentelemetry.sdk.trace.sampling import Decision, TraceIdRatioBased from opentelemetry.trace import Tracer @@ -98,6 +99,7 @@ def __new__( should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, + span_exporter: Optional[SpanExporter] = None, ) -> "LangfuseResourceManager": if public_key in cls._instances: return cls._instances[public_key] @@ -133,6 +135,7 @@ def __new__( should_export_span=should_export_span, additional_headers=additional_headers, tracer_provider=tracer_provider, + span_exporter=span_exporter, ) cls._instances[public_key] = instance @@ -159,6 +162,7 @@ def _initialize_instance( should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, + span_exporter: Optional[SpanExporter] = None, ) -> None: self.public_key = public_key self.secret_key = secret_key @@ -177,6 +181,7 @@ def _initialize_instance( self.blocked_instrumentation_scopes = blocked_instrumentation_scopes self.should_export_span = should_export_span self.additional_headers = additional_headers + self.span_exporter = span_exporter self.tracer_provider: Optional[TracerProvider] = None # OTEL Tracer @@ -196,6 +201,7 @@ def _initialize_instance( blocked_instrumentation_scopes=blocked_instrumentation_scopes, should_export_span=should_export_span, additional_headers=additional_headers, + span_exporter=span_exporter, ) tracer_provider.add_span_processor(langfuse_processor) diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index 1057137af..a684a8813 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -19,7 +19,7 @@ from opentelemetry.context import Context from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import ReadableSpan, Span -from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter from opentelemetry.trace import format_span_id from langfuse._client.environment_variables import ( @@ -63,6 +63,7 @@ def __init__( blocked_instrumentation_scopes: Optional[List[str]] = None, should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, + span_exporter: Optional[SpanExporter] = None, ): self.public_key = public_key self.blocked_instrumentation_scopes = ( @@ -82,37 +83,38 @@ def __init__( else None ) - basic_auth_header = "Basic " + base64.b64encode( - f"{public_key}:{secret_key}".encode("utf-8") - ).decode("ascii") + if span_exporter is None: + basic_auth_header = "Basic " + base64.b64encode( + f"{public_key}:{secret_key}".encode("utf-8") + ).decode("ascii") - # Prepare default headers - default_headers = { - "Authorization": basic_auth_header, - "x-langfuse-sdk-name": "python", - "x-langfuse-sdk-version": langfuse_version, - "x-langfuse-public-key": public_key, - } + # Prepare default headers + default_headers = { + "Authorization": basic_auth_header, + "x-langfuse-sdk-name": "python", + "x-langfuse-sdk-version": langfuse_version, + "x-langfuse-public-key": public_key, + } - # Merge additional headers if provided - headers = {**default_headers, **(additional_headers or {})} + # Merge additional headers if provided + headers = {**default_headers, **(additional_headers or {})} - traces_export_path = os.environ.get(LANGFUSE_OTEL_TRACES_EXPORT_PATH, None) + traces_export_path = os.environ.get(LANGFUSE_OTEL_TRACES_EXPORT_PATH, None) - endpoint = ( - f"{base_url}/{traces_export_path}" - if traces_export_path - else f"{base_url}/api/public/otel/v1/traces" - ) + endpoint = ( + f"{base_url}/{traces_export_path}" + if traces_export_path + else f"{base_url}/api/public/otel/v1/traces" + ) - langfuse_span_exporter = OTLPSpanExporter( - endpoint=endpoint, - headers=headers, - timeout=timeout, - ) + span_exporter = OTLPSpanExporter( + endpoint=endpoint, + headers=headers, + timeout=timeout, + ) super().__init__( - span_exporter=langfuse_span_exporter, + span_exporter=span_exporter, export_timeout_millis=timeout * 1_000 if timeout else None, max_export_batch_size=flush_at, schedule_delay_millis=flush_interval * 1_000 diff --git a/tests/test_additional_headers_simple.py b/tests/test_additional_headers_simple.py index 47cc765bd..dd843b35a 100644 --- a/tests/test_additional_headers_simple.py +++ b/tests/test_additional_headers_simple.py @@ -3,11 +3,25 @@ This module tests that additional headers are properly configured in the HTTP clients. """ +from typing import Sequence + import httpx +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from langfuse._client.client import Langfuse +class NoOpSpanExporter(SpanExporter): + """Minimal exporter used to verify custom exporter injection.""" + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + return SpanExportResult.SUCCESS + + def shutdown(self) -> None: + pass + + class TestAdditionalHeadersSimple: """Simple test suite for additional_headers functionality.""" @@ -196,3 +210,19 @@ def test_span_processor_none_additional_headers_works(self): assert "Authorization" in exporter._headers assert "x-langfuse-sdk-name" in exporter._headers assert "x-langfuse-public-key" in exporter._headers + + def test_span_processor_uses_custom_span_exporter_when_provided(self): + """Test that a custom exporter bypasses the default OTLP exporter construction.""" + from langfuse._client.span_processor import LangfuseSpanProcessor + + custom_exporter = NoOpSpanExporter() + + processor = LangfuseSpanProcessor( + public_key="test-public-key", + secret_key="test-secret-key", + base_url="https://mock-host.com", + additional_headers={"X-Custom-Trace-Header": "trace-value"}, + span_exporter=custom_exporter, + ) + + assert processor.span_exporter is custom_exporter diff --git a/tests/test_resource_manager.py b/tests/test_resource_manager.py index 72f9f7d7e..cce6446e9 100644 --- a/tests/test_resource_manager.py +++ b/tests/test_resource_manager.py @@ -1,10 +1,25 @@ """Test the LangfuseResourceManager and get_client() function.""" +from typing import Sequence + +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult + from langfuse import Langfuse from langfuse._client.get_client import get_client from langfuse._client.resource_manager import LangfuseResourceManager +class NoOpSpanExporter(SpanExporter): + """Minimal exporter used to verify configuration propagation.""" + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + return SpanExportResult.SUCCESS + + def shutdown(self) -> None: + pass + + def test_get_client_preserves_all_settings(): """Test that get_client() preserves environment and all client settings.""" with LangfuseResourceManager._lock: @@ -13,7 +28,11 @@ def test_get_client_preserves_all_settings(): def should_export(span): return span.name != "drop" + span_exporter = NoOpSpanExporter() + settings = { + "public_key": "pk-comprehensive", + "secret_key": "sk-comprehensive", "environment": "test-env", "release": "v1.2.3", "timeout": 30, @@ -21,6 +40,7 @@ def should_export(span): "sample_rate": 0.8, "should_export_span": should_export, "additional_headers": {"X-Custom": "value"}, + "span_exporter": span_exporter, } original_client = Langfuse(**settings) @@ -36,6 +56,7 @@ def should_export(span): assert rm.sample_rate == settings["sample_rate"] assert rm.should_export_span is should_export assert rm.additional_headers == settings["additional_headers"] + assert rm.span_exporter is span_exporter original_client.shutdown() @@ -49,6 +70,9 @@ def should_export_a(span): def should_export_b(span): return span.name.startswith("b") + exporter_a = NoOpSpanExporter() + exporter_b = NoOpSpanExporter() + # Settings for client A settings_a = { "public_key": "pk-comprehensive-a", @@ -58,6 +82,7 @@ def should_export_b(span): "timeout": 10, "sample_rate": 0.5, "should_export_span": should_export_a, + "span_exporter": exporter_a, } # Settings for client B @@ -69,6 +94,7 @@ def should_export_b(span): "timeout": 20, "sample_rate": 0.9, "should_export_span": should_export_b, + "span_exporter": exporter_b, } client_a = Langfuse(**settings_a) @@ -91,6 +117,8 @@ def should_export_b(span): assert retrieved_b._resources.release == settings_b["release"] assert retrieved_a._resources.should_export_span is should_export_a assert retrieved_b._resources.should_export_span is should_export_b + assert retrieved_a._resources.span_exporter is exporter_a + assert retrieved_b._resources.span_exporter is exporter_b client_a.shutdown() client_b.shutdown() From cf2f7cd042b80abcb9f2208dbf861ee833ca3c3c Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 10 Apr 2026 11:55:20 +0000 Subject: [PATCH 245/296] chore: release v4.2.0 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e6e97a6b0..ea7f49f81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.1.0" +version = "4.2.0" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 24b2877c6..a74858998 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-02T14:44:34.959905188Z" +exclude-newer = "2026-04-03T11:55:15.924547735Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.1.0" +version = "4.2.0" source = { editable = "." } dependencies = [ { name = "backoff" }, From 270f729991ed447d5d4b803e219781e718eb65dd Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 10 Apr 2026 15:04:56 +0200 Subject: [PATCH 246/296] feat(api): update API spec from langfuse/langfuse 07cae52 (#1617) Co-authored-by: langfuse-bot Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/api/blob_storage_integrations/client.py | 4 ++-- langfuse/api/blob_storage_integrations/raw_client.py | 4 ++-- .../types/create_blob_storage_integration_request.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index a8d693cec..21eeffde3 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -108,7 +108,7 @@ def upsert_blob_storage_integration( type : BlobStorageIntegrationType bucket_name : str - Name of the storage bucket + Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens only, must start and end with a letter or number, no consecutive hyphens). region : str Storage region @@ -367,7 +367,7 @@ async def upsert_blob_storage_integration( type : BlobStorageIntegrationType bucket_name : str - Name of the storage bucket + Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens only, must start and end with a letter or number, no consecutive hyphens). region : str Storage region diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 30e486209..5833ea63e 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -165,7 +165,7 @@ def upsert_blob_storage_integration( type : BlobStorageIntegrationType bucket_name : str - Name of the storage bucket + Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens only, must start and end with a letter or number, no consecutive hyphens). region : str Storage region @@ -642,7 +642,7 @@ async def upsert_blob_storage_integration( type : BlobStorageIntegrationType bucket_name : str - Name of the storage bucket + Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens only, must start and end with a letter or number, no consecutive hyphens). region : str Storage region diff --git a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py index ce23cd246..ada6e432b 100644 --- a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +++ b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py @@ -26,7 +26,7 @@ class CreateBlobStorageIntegrationRequest(UniversalBaseModel): pydantic.Field() ) """ - Name of the storage bucket + Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens only, must start and end with a letter or number, no consecutive hyphens). """ endpoint: typing.Optional[str] = pydantic.Field(default=None) From 542f2c983dab4ada10af91602acb0439939bcec3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:05:29 +0200 Subject: [PATCH 247/296] chore(deps): bump actions/github-script from 8.0.0 to 9.0.0 in the github-actions group (#1616) chore(deps): bump actions/github-script in the github-actions group Bumps the github-actions group with 1 update: [actions/github-script](https://github.com/actions/github-script). Updates `actions/github-script` from 8.0.0 to 9.0.0 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/ed597411d8f924073f98dfc5c65a23a2325f34cd...3a2844b7e9c422d3c10d287c895573f7108da1b3) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/claude-review-maintainer-prs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review-maintainer-prs.yml b/.github/workflows/claude-review-maintainer-prs.yml index 977ff4a4b..1e1034106 100644 --- a/.github/workflows/claude-review-maintainer-prs.yml +++ b/.github/workflows/claude-review-maintainer-prs.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Check author permission and existing review request id: check - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const owner = context.repo.owner; @@ -57,7 +57,7 @@ jobs: - name: Add Claude review comment if: steps.check.outputs.should_comment == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | await github.rest.issues.createComment({ From dea5fdfbad2e51310224eb28e5d5ab5f47884ea1 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:01:57 +0200 Subject: [PATCH 248/296] refactor(tests): split suites by execution level and speed up CI (#1609) --- .github/workflows/ci.yml | 198 ++- AGENTS.md | 149 ++ CLAUDE.md | 141 -- langfuse/_client/client.py | 3 +- langfuse/_client/resource_manager.py | 2 + langfuse/_task_manager/media_manager.py | 15 + .../_task_manager/score_ingestion_consumer.py | 13 +- langfuse/_utils/prompt_cache.py | 105 +- pyproject.toml | 6 + scripts/select_e2e_shard.py | 114 ++ tests/api_wrapper.py | 38 - tests/conftest.py | 167 ++ tests/e2e/__init__.py | 1 + tests/{ => e2e}/test_batch_evaluation.py | 36 +- tests/{ => e2e}/test_core_sdk.py | 147 +- tests/{ => e2e}/test_datasets.py | 60 +- tests/{ => e2e}/test_decorators.py | 595 ++++--- tests/{ => e2e}/test_experiments.py | 8 +- tests/e2e/test_media.py | 75 + tests/e2e/test_prompt.py | 697 ++++++++ tests/live_provider/__init__.py | 1 + tests/{ => live_provider}/test_langchain.py | 67 +- .../test_langchain_integration.py | 4 +- tests/{ => live_provider}/test_openai.py | 18 +- tests/live_provider/test_prompt.py | 87 + tests/support/__init__.py | 1 + tests/support/api_wrapper.py | 130 ++ tests/support/retry.py | 65 + tests/support/utils.py | 110 ++ tests/test_prompt.py | 1524 ----------------- tests/unit/__init__.py | 1 + .../test_additional_headers_simple.py | 0 tests/unit/test_e2e_sharding.py | 54 + tests/unit/test_e2e_support.py | 173 ++ tests/{ => unit}/test_error_logging.py | 0 tests/{ => unit}/test_error_parsing.py | 0 tests/{ => unit}/test_initialization.py | 0 tests/{ => unit}/test_json.py | 0 tests/unit/test_langchain.py | 168 ++ tests/{ => unit}/test_logger.py | 0 tests/{ => unit}/test_media.py | 62 - tests/{ => unit}/test_media_manager.py | 0 tests/unit/test_openai.py | 238 +++ .../test_openai_prompt_extraction.py | 0 tests/{ => unit}/test_otel.py | 34 +- tests/{ => unit}/test_parse_usage_model.py | 0 tests/unit/test_prompt.py | 750 ++++++++ tests/{ => unit}/test_prompt_atexit.py | 8 +- tests/{ => unit}/test_prompt_compilation.py | 0 tests/{ => unit}/test_propagate_attributes.py | 15 +- tests/{ => unit}/test_resource_manager.py | 78 +- tests/{ => unit}/test_serializer.py | 0 tests/{ => unit}/test_span_filter.py | 0 tests/{ => unit}/test_utils.py | 0 tests/{ => unit}/test_version.py | 0 tests/utils.py | 25 - 56 files changed, 3934 insertions(+), 2249 deletions(-) create mode 100644 AGENTS.md delete mode 100644 CLAUDE.md create mode 100644 scripts/select_e2e_shard.py delete mode 100644 tests/api_wrapper.py create mode 100644 tests/conftest.py create mode 100644 tests/e2e/__init__.py rename tests/{ => e2e}/test_batch_evaluation.py (96%) rename tests/{ => e2e}/test_core_sdk.py (94%) rename tests/{ => e2e}/test_datasets.py (84%) rename tests/{ => e2e}/test_decorators.py (79%) rename tests/{ => e2e}/test_experiments.py (99%) create mode 100644 tests/e2e/test_media.py create mode 100644 tests/e2e/test_prompt.py create mode 100644 tests/live_provider/__init__.py rename tests/{ => live_provider}/test_langchain.py (94%) rename tests/{ => live_provider}/test_langchain_integration.py (99%) rename tests/{ => live_provider}/test_openai.py (99%) create mode 100644 tests/live_provider/test_prompt.py create mode 100644 tests/support/__init__.py create mode 100644 tests/support/api_wrapper.py create mode 100644 tests/support/retry.py create mode 100644 tests/support/utils.py delete mode 100644 tests/test_prompt.py create mode 100644 tests/unit/__init__.py rename tests/{ => unit}/test_additional_headers_simple.py (100%) create mode 100644 tests/unit/test_e2e_sharding.py create mode 100644 tests/unit/test_e2e_support.py rename tests/{ => unit}/test_error_logging.py (100%) rename tests/{ => unit}/test_error_parsing.py (100%) rename tests/{ => unit}/test_initialization.py (100%) rename tests/{ => unit}/test_json.py (100%) create mode 100644 tests/unit/test_langchain.py rename tests/{ => unit}/test_logger.py (100%) rename tests/{ => unit}/test_media.py (69%) rename tests/{ => unit}/test_media_manager.py (100%) create mode 100644 tests/unit/test_openai.py rename tests/{ => unit}/test_openai_prompt_extraction.py (100%) rename tests/{ => unit}/test_otel.py (99%) rename tests/{ => unit}/test_parse_usage_model.py (100%) create mode 100644 tests/unit/test_prompt.py rename tests/{ => unit}/test_prompt_atexit.py (93%) rename tests/{ => unit}/test_prompt_compilation.py (100%) rename tests/{ => unit}/test_propagate_attributes.py (99%) rename tests/{ => unit}/test_resource_manager.py (64%) rename tests/{ => unit}/test_serializer.py (100%) rename tests/{ => unit}/test_span_filter.py (100%) rename tests/{ => unit}/test_utils.py (100%) rename tests/{ => unit}/test_version.py (100%) delete mode 100644 tests/utils.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a95ec5e38..fdac4226a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false + cancel-in-progress: true jobs: linting: @@ -52,17 +52,14 @@ jobs: - name: Run mypy type checking run: uv run --frozen mypy langfuse --no-error-summary - ci: + unit-tests: runs-on: blacksmith-4vcpu-ubuntu-2404 timeout-minutes: 30 env: LANGFUSE_BASE_URL: "http://localhost:3000" - LANGFUSE_PUBLIC_KEY: "pk-lf-1234567890" - LANGFUSE_SECRET_KEY: "sk-lf-1234567890" - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - # SERPAPI_API_KEY: ${{ secrets.SERPAPI_API_KEY }} - HUGGINGFACEHUB_API_TOKEN: ${{ secrets.HUGGINGFACEHUB_API_TOKEN }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + LANGFUSE_PUBLIC_KEY: "pk-lf-test" + LANGFUSE_SECRET_KEY: "sk-lf-test" + OPENAI_API_KEY: "test-openai-key" strategy: fail-fast: false matrix: @@ -73,56 +70,95 @@ jobs: - "3.13" - "3.14" - name: Test on Python version ${{ matrix.python-version }} + name: Unit tests on Python ${{ matrix.python-version }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 + - name: Install uv and set Python version + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: - version: 10.33.0 + version: "0.11.2" + python-version: ${{ matrix.python-version }} + enable-cache: true + + - name: Check Python version + run: python --version - - name: Clone langfuse server + - name: Install the project dependencies + run: uv sync --locked + + - name: Run the automated tests run: | - git clone https://github.com/langfuse/langfuse.git ./langfuse-server && echo $(cd ./langfuse-server && git rev-parse HEAD) + python --version + uv run --frozen pytest -n auto --dist worksteal -s -v --log-cli-level=INFO tests/unit - - name: Setup node (for langfuse server) - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: 24 + e2e-tests: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - suite: e2e + job_name: E2E shard 1 tests on Python 3.13 + shard_name: shard-1 + shard_index: 0 + shard_count: 2 + - suite: e2e + job_name: E2E shard 2 tests on Python 3.13 + shard_name: shard-2 + shard_index: 1 + shard_count: 2 + - suite: live_provider + job_name: E2E live-provider tests on Python 3.13 + shard_name: live-provider + env: + LANGFUSE_BASE_URL: "http://localhost:3000" + LANGFUSE_PUBLIC_KEY: "pk-lf-1234567890" + LANGFUSE_SECRET_KEY: "sk-lf-1234567890" + LANGFUSE_INIT_ORG_ID: "0c6c96f4-0ca0-4f16-92a8-6dd7d7c6a501" + LANGFUSE_INIT_ORG_NAME: "SDK Test Org" + LANGFUSE_INIT_PROJECT_ID: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" + LANGFUSE_INIT_PROJECT_NAME: "SDK Test Project" + LANGFUSE_INIT_PROJECT_PUBLIC_KEY: "pk-lf-1234567890" + LANGFUSE_INIT_PROJECT_SECRET_KEY: "sk-lf-1234567890" + LANGFUSE_INIT_USER_EMAIL: "sdk-tests@langfuse.local" + LANGFUSE_INIT_USER_NAME: "SDK Tests" + LANGFUSE_INIT_USER_PASSWORD: "langfuse-ci-password" + LANGFUSE_E2E_READ_TIMEOUT_SECONDS: "60" + LANGFUSE_E2E_READ_INTERVAL_SECONDS: "0.5" + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + # SERPAPI_API_KEY: ${{ secrets.SERPAPI_API_KEY }} + HUGGINGFACEHUB_API_TOKEN: ${{ secrets.HUGGINGFACEHUB_API_TOKEN }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - - name: Cache langfuse server dependencies - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + name: ${{ matrix.job_name }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install uv and set Python version + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: - path: ./langfuse-server/node_modules - key: langfuse-serve-${{ hashFiles('langfuse-server/pnpm-lock.yaml') }} - restore-keys: | - langfuse-serve- + version: "0.11.2" + python-version: "3.13" + enable-cache: true + - name: Install the project dependencies + run: uv sync --locked + - name: Check uv Python version + run: uv run --frozen python --version + - name: Prepare langfuse server compose + run: | + mkdir -p ./langfuse-server + LANGFUSE_SERVER_SHA="$(git ls-remote https://github.com/langfuse/langfuse.git HEAD | cut -f1)" + curl -fsSL "https://raw.githubusercontent.com/langfuse/langfuse/${LANGFUSE_SERVER_SHA}/docker-compose.yml" \ + -o ./langfuse-server/docker-compose.yml + echo "${LANGFUSE_SERVER_SHA}" - name: Run langfuse server run: | cd ./langfuse-server - echo "::group::Run langfuse server" - TELEMETRY_ENABLED=false docker compose up -d postgres - echo "::endgroup::" - - echo "::group::Logs from langfuse server" - TELEMETRY_ENABLED=false docker compose logs - echo "::endgroup::" - - echo "::group::Install dependencies (necessary to run seeder)" - pnpm i - echo "::endgroup::" - - echo "::group::Seed db" - cp .env.dev.example .env - pnpm run db:migrate - pnpm run db:seed - echo "::endgroup::" - rm -rf .env - - echo "::group::Run server" - + echo "::group::Start langfuse server" TELEMETRY_ENABLED=false \ + NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=true \ LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 \ LANGFUSE_INGESTION_QUEUE_DELAY_MS=10 \ LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=10 \ @@ -131,22 +167,22 @@ jobs: LANGFUSE_ENABLE_EVENTS_TABLE_V2_APIS=true \ LANGFUSE_ENABLE_EVENTS_TABLE_OBSERVATIONS=true \ docker compose up -d - echo "::endgroup::" - # Add this step to check the health of the container - name: Health check for langfuse server run: | echo "Checking if the langfuse server is up..." retry_count=0 - max_retries=10 - until curl --output /dev/null --silent --head --fail http://localhost:3000/api/public/health + max_retries=20 + until curl --output /dev/null --silent --head --fail http://localhost:3000/api/public/health && \ + uv run --frozen python -c "from langfuse import Langfuse; client = Langfuse(); project_id = client._get_project_id(); assert project_id == '7a88fb47-b4e2-43b8-a06c-a5ce950dc53a', project_id; print(project_id)" do retry_count=`expr $retry_count + 1` echo "Attempt $retry_count of $max_retries..." if [ $retry_count -ge $max_retries ]; then echo "Langfuse server did not respond in time. Printing logs..." - docker logs langfuse-server-langfuse-web-1 + (cd ./langfuse-server && docker compose ps) + (cd ./langfuse-server && docker compose logs langfuse-web langfuse-worker) echo "Failing the step..." exit 1 fi @@ -154,28 +190,58 @@ jobs: done echo "Langfuse server is up and running!" - - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 - with: - version: "0.11.2" - python-version: ${{ matrix.python-version }} - enable-cache: true - - - name: Check Python version - run: python --version - - - name: Install the project dependencies - run: uv sync --locked - - - name: Run the automated tests + - name: Select e2e shard files + if: ${{ matrix.suite == 'e2e' }} run: | - python --version - uv run --frozen pytest -n auto --dist loadfile -s -v --log-cli-level=INFO + uv run --frozen python scripts/select_e2e_shard.py \ + --shard-index ${{ matrix.shard_index }} \ + --shard-count ${{ matrix.shard_count }} \ + --json + uv run --frozen python scripts/select_e2e_shard.py \ + --shard-index ${{ matrix.shard_index }} \ + --shard-count ${{ matrix.shard_count }} \ + > "$RUNNER_TEMP/e2e-shard-files.txt" + cat "$RUNNER_TEMP/e2e-shard-files.txt" + + - name: Run the parallel end-to-end tests + if: ${{ matrix.suite == 'e2e' }} + run: | + uv run --frozen python --version + mapfile -t e2e_files < "$RUNNER_TEMP/e2e-shard-files.txt" + set +e + uv run --frozen pytest -n 4 --dist worksteal -s -v --log-cli-level=INFO "${e2e_files[@]}" -m "not serial_e2e" + status=$? + set -e + if [ "$status" -eq 5 ]; then + echo "No parallel e2e tests selected for this shard." + elif [ "$status" -ne 0 ]; then + exit "$status" + fi + + - name: Run serial end-to-end tests + if: ${{ matrix.suite == 'e2e' }} + run: | + mapfile -t e2e_files < "$RUNNER_TEMP/e2e-shard-files.txt" + set +e + uv run --frozen pytest -s -v --log-cli-level=INFO "${e2e_files[@]}" -m "serial_e2e" + status=$? + set -e + if [ "$status" -eq 5 ]; then + echo "No serial e2e tests selected for this shard." + elif [ "$status" -ne 0 ]; then + exit "$status" + fi + + - name: Run live-provider tests + if: ${{ matrix.suite == 'live_provider' }} + run: | + uv run --frozen python --version + uv run --frozen pytest -n 4 --dist worksteal -s -v --log-cli-level=INFO tests/live_provider -m "live_provider" all-tests-passed: # This allows us to have a branch protection rule for tests and deploys with matrix runs-on: blacksmith-2vcpu-ubuntu-2404 - needs: [ci, linting, type-checking] + needs: [unit-tests, e2e-tests, linting, type-checking] if: always() steps: - name: Successful deploy diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..eff827d76 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,149 @@ +# AGENTS.md + +Shared instructions for coding agents working in this repository. + +Keep this file concise, concrete, and repo-specific. If guidance grows large, split it into referenced docs instead of turning this file into a handbook. + +## Maintenance Contract + +- `AGENTS.md` is a living document. +- Update it in the same PR when repo-wide workflows, architecture, CI contracts, release processes, or durable coding defaults materially change. +- Do not edit this file for one-off task preferences. +- Keep this file as the canonical shared agent guide for this repository. + +## Project Summary + +This repository contains the Langfuse Python SDK. + +- `langfuse/_client/`: core SDK, OpenTelemetry integration, resource management, decorators, datasets +- `langfuse/openai.py`: OpenAI instrumentation +- `langfuse/langchain/`: LangChain integration +- `langfuse/_task_manager/`: background consumers for media and score ingestion +- `langfuse/api/`: generated Fern API client, do not hand-edit +- `tests/unit/`: deterministic local tests, no Langfuse server +- `tests/e2e/`: real Langfuse-server tests +- `tests/live_provider/`: live OpenAI / LangChain provider tests +- `tests/support/`: shared helpers for e2e tests +- `scripts/select_e2e_shard.py`: CI shard selector for `tests/e2e` + +## Working Style + +- Prefer small, targeted changes that preserve existing behavior. +- Do not weaken assertions just to make tests faster or greener. +- If a test is slow, first optimize setup, teardown, polling, or fixtures. +- Keep repo-shared instructions here. Keep personal or machine-specific notes out of version control. +- Keep tests independent and parallel-safe by default. +- For bug fixes, prefer writing or identifying the failing test first, confirm the failure, then implement the fix. + +## Setup And Quality Commands + +```bash +uv sync --locked +uv run pre-commit install +uv run --frozen ruff check . +uv run --frozen ruff format . +uv run --frozen mypy langfuse --no-error-summary +``` + +## Test Commands + +Use the directory-based test split. + +```bash +# Unit tests +uv run --frozen pytest -n auto --dist worksteal tests/unit + +# All e2e tests that can run concurrently +uv run --frozen pytest -n 4 --dist worksteal tests/e2e -m "not serial_e2e" + +# E2E tests that must run serially +uv run --frozen pytest tests/e2e -m "serial_e2e" + +# Live provider tests +uv run --frozen pytest -n 4 --dist worksteal tests/live_provider -m "live_provider" + +# Single test +uv run --frozen pytest tests/unit/test_resource_manager.py::test_pause_signals_score_consumer_shutdown +``` + +## Test Topology + +### `tests/unit` + +- Must not require a running Langfuse server. +- Prefer in-memory exporters and local fakes over real network calls. +- If tracing behavior is under test, use the shared in-memory fixtures in `tests/conftest.py`. + +### `tests/e2e` + +- Use for persisted backend behavior that genuinely needs a real Langfuse server. +- Prefer bounded polling helpers in `tests/support/` over raw `sleep()` calls. +- Use `serial_e2e` only for tests that are unsafe under shared-server concurrency. +- New e2e files should be named `tests/e2e/test_*.py`. +- Do not add `e2e_core` / `e2e_data` markers. CI shards `tests/e2e` mechanically with `scripts/select_e2e_shard.py`. + +### `tests/live_provider` + +- This suite uses real provider calls and always runs as one dedicated CI suite. +- Do not split or shard `tests/live_provider` into separate smoke and extended jobs unless the team explicitly changes that policy. +- Keep assertions focused on stable provider-facing behavior rather than brittle observation counts. + +## CI Contract + +The main CI workflow currently runs: +- linting on Python 3.13 +- mypy on Python 3.13 +- `tests/unit` on a Python 3.10-3.14 matrix +- `tests/e2e` in 2 mechanical shards plus a serial subset inside each shard +- `tests/live_provider` as one always-on suite + +If you change the e2e split: + +- update `scripts/select_e2e_shard.py`, not marker routing in `tests/conftest.py` +- make sure new `tests/e2e/test_*.py` files are automatically covered +- keep `serial_e2e` as the only scheduling-specific pytest marker + +If you change CI bootstrap: + +- preserve the `LANGFUSE_INIT_*` startup path for the Langfuse server unless there is a strong reason to change it +- preserve `cancel-in-progress: true` + +## Repo Rules + +- Keep changes scoped. Avoid unrelated refactors. +- Prefer `LANGFUSE_BASE_URL`; `LANGFUSE_HOST` is deprecated and is only kept for compatibility tests. +- If you touch `langfuse/api/`, regenerate it from the upstream Fern/OpenAPI source instead of hand-editing files. +- If you touch shutdown, flushing, or worker-thread behavior, run the relevant resource-manager and OTEL-heavy tests. +- If you change OpenAI or LangChain instrumentation, keep as much coverage as possible in `tests/unit` using exporter-local assertions, and leave only the minimal necessary coverage in `tests/e2e` / `tests/live_provider`. +- Never commit secrets or credentials. +- Keep `.env.template` in sync with required local-development environment variables. + +## Commit And PR Rules + +- Commit messages and PR titles should follow Conventional Commits: `type(scope): description` or `type: description`. +- Keep commits focused and atomic. +- In PR descriptions, list the main verification commands you ran. + +## Python-Specific Notes + +- Exception messages should not inline f-string literals in the `raise` statement. Build the message in a variable first. +- Prefer ASCII-only edits unless the file already uses Unicode or Unicode is clearly required. + +## Release And Docs + +```bash +uv build --no-sources +uv run --group docs pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse +``` + +Releases are handled by GitHub Actions. Do not build an ad hoc local release flow into repository instructions. + +## External Docs + +- Prefer official documentation first when answering product or API questions. +- For OpenAI API, ChatGPT Apps SDK, or Codex questions, use the official OpenAI developer docs or Docs MCP server if available. + +## Git Safety + +- Do not use destructive git commands such as `git reset --hard` unless explicitly requested. +- Do not revert unrelated working-tree changes. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 330193c59..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,141 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is the Langfuse Python SDK, a client library for accessing the Langfuse observability platform. The SDK provides integration with OpenTelemetry (OTel) for tracing, automatic instrumentation for popular LLM frameworks (OpenAI, Langchain, etc.), and direct API access to Langfuse's features. - -## Development Commands - -### Setup - -```bash -# Install the project and development dependencies -uv sync - -# Setup pre-commit hooks -uv run pre-commit install -``` - -### Testing - -```bash -# Run all tests with verbose output -uv run --env-file .env pytest -s -v --log-cli-level=INFO - -# Run a specific test -uv run --env-file .env pytest -s -v --log-cli-level=INFO tests/test_core_sdk.py::test_flush - -# Run tests in parallel (faster) -uv run --env-file .env pytest -s -v --log-cli-level=INFO -n auto -``` - -### Code Quality - -```bash -# Format code with Ruff -uv run ruff format . - -# Run linting (development config) -uv run ruff check . - -# Run type checking -uv run mypy . - -# Run pre-commit hooks manually -uv run pre-commit run --all-files -``` - -### Building and Releasing - -```bash -# Build the package locally (for testing) -uv build --no-sources - -# Generate documentation -uv run --group docs pdoc -o docs/ --docformat google --logo "https://langfuse.com/langfuse_logo.svg" langfuse -``` - -Releases are automated via GitHub Actions. To release: - -1. Go to Actions > "Release Python SDK" workflow -2. Click "Run workflow" -3. Select version bump type (patch/minor/major/prepatch/preminor/premajor) -4. For prereleases, select the type (alpha/beta/rc) - -The workflow handles versioning, building, PyPI publishing (via OIDC), and GitHub release creation. - -## Architecture - -### Core Components - -- **`langfuse/_client/`**: Main SDK implementation built on OpenTelemetry - - `client.py`: Core Langfuse client with OTel integration - - `span.py`: LangfuseSpan, LangfuseGeneration, LangfuseEvent classes - - `observe.py`: Decorator for automatic instrumentation - - `datasets.py`: Dataset management functionality - -- **`langfuse/api/`**: Auto-generated Fern API client - - Contains all API resources and types - - Generated from OpenAPI spec - do not manually edit these files - -- **`langfuse/_task_manager/`**: Background processing - - Media upload handling and queue management - - Score ingestion consumer - -- **Integration modules**: - - `langfuse/openai.py`: OpenAI instrumentation - - `langfuse/langchain/`: Langchain integration via CallbackHandler - -### Key Design Patterns - -The SDK is built on OpenTelemetry for observability, using: - -- Spans for tracing LLM operations -- Attributes for metadata (see `LangfuseOtelSpanAttributes`) -- Resource management for efficient batching and flushing - -The client follows an async-first design with automatic batching of events and background flushing to the Langfuse API. - -## Configuration - -Environment variables (defined in `_client/environment_variables.py`): - -- `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY`: API credentials -- `LANGFUSE_HOST`: API endpoint (defaults to https://cloud.langfuse.com) -- `LANGFUSE_DEBUG`: Enable debug logging -- `LANGFUSE_TRACING_ENABLED`: Enable/disable tracing -- `LANGFUSE_SAMPLE_RATE`: Sampling rate for traces - -## Testing Notes - -- Create `.env` file based on `.env.template` for integration tests -- E2E tests with external APIs (OpenAI, SERP) are typically skipped in CI -- Remove `@pytest.mark.skip` decorators in test files to run external API tests -- Tests use `respx` for HTTP mocking and `pytest-httpserver` for test servers - -## Important Files - -- `pyproject.toml`: uv project metadata, dependencies, and tool settings -- `uv.lock`: Locked dependency graph for local development and CI - -## API Generation - -The `langfuse/api/` directory is auto-generated from the Langfuse OpenAPI specification using Fern. To update: - -1. Generate new SDK in main Langfuse repo -2. Copy generated files from `generated/python` to `langfuse/api/` -3. Run `uv run ruff format .` to format the generated code - -## Testing Guidelines - -### Approach to Test Changes - -- Don't remove functionality from existing unit tests just to make tests pass. Only change the test, if underlying code changes warrant a test change. - -## Python Code Rules - -### Exception Handling - -- Exception must not use an f-string literal, assign to variable first diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index f0b469320..d7698739c 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -3486,8 +3486,9 @@ def refresh_task() -> None: fetch_timeout_seconds=fetch_timeout_seconds, ) - self._resources.prompt_cache.add_refresh_prompt_task( + self._resources.prompt_cache.add_refresh_prompt_task_if_current( cache_key, + cached_prompt, refresh_task, ) langfuse_logger.debug( diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 6e277e0db..2d42f6ce1 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -405,6 +405,8 @@ def _stop_and_join_consumer_threads(self) -> None: for media_upload_consumer in self._media_upload_consumers: media_upload_consumer.pause() + self._media_manager.signal_shutdown(count=len(self._media_upload_consumers)) + for media_upload_consumer in self._media_upload_consumers: try: media_upload_consumer.join() diff --git a/langfuse/_task_manager/media_manager.py b/langfuse/_task_manager/media_manager.py index d1da17a27..7a7123798 100644 --- a/langfuse/_task_manager/media_manager.py +++ b/langfuse/_task_manager/media_manager.py @@ -18,6 +18,7 @@ T = TypeVar("T") P = ParamSpec("P") +_SHUTDOWN_SENTINEL = object() class MediaManager: @@ -40,6 +41,11 @@ def __init__( def process_next_media_upload(self) -> None: try: upload_job = self._queue.get(block=True, timeout=1) + + if upload_job is _SHUTDOWN_SENTINEL: + self._queue.task_done() + return + logger.debug( f"Media: Processing upload for media_id={upload_job['media_id']} in trace_id={upload_job['trace_id']}" ) @@ -54,6 +60,15 @@ def process_next_media_upload(self) -> None: ) self._queue.task_done() + def signal_shutdown(self, *, count: int = 1) -> None: + for _ in range(count): + try: + self._queue.put(_SHUTDOWN_SENTINEL, block=False) + except Full: + # If the queue is full, the consumer will keep draining work and + # observe the paused flag on the next loop iteration. + break + def _find_and_process_media( self, *, diff --git a/langfuse/_task_manager/score_ingestion_consumer.py b/langfuse/_task_manager/score_ingestion_consumer.py index dcb575263..ea5c2b34e 100644 --- a/langfuse/_task_manager/score_ingestion_consumer.py +++ b/langfuse/_task_manager/score_ingestion_consumer.py @@ -2,7 +2,7 @@ import os import threading import time -from queue import Empty, Queue +from queue import Empty, Full, Queue from typing import Any, List, Optional import backoff @@ -17,6 +17,7 @@ MAX_EVENT_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_EVENT_SIZE_BYTES", 1_000_000)) MAX_BATCH_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_BATCH_SIZE_BYTES", 2_500_000)) +_SHUTDOWN_SENTINEL = object() class ScoreIngestionMetadata(BaseModel): @@ -71,6 +72,10 @@ def _next(self) -> list: block=True, timeout=self._flush_interval - elapsed ) + if event is _SHUTDOWN_SENTINEL: + self._ingestion_queue.task_done() + break + # convert pydantic models to dicts if "body" in event and isinstance(event["body"], BaseModel): event["body"] = event["body"].model_dump(exclude_none=True) @@ -139,6 +144,12 @@ def upload(self) -> None: def pause(self) -> None: """Pause the consumer.""" self.running = False + try: + self._ingestion_queue.put(_SHUTDOWN_SENTINEL, block=False) + except Full: + # If the queue is full, the consumer will wake up naturally while + # draining items, so a dedicated shutdown signal is not required. + pass def _upload_batch(self, batch: List[Any]) -> None: logger.debug( diff --git a/langfuse/_utils/prompt_cache.py b/langfuse/_utils/prompt_cache.py index ef465b038..7d9c2298b 100644 --- a/langfuse/_utils/prompt_cache.py +++ b/langfuse/_utils/prompt_cache.py @@ -3,8 +3,8 @@ import atexit import os from datetime import datetime -from queue import Empty, Queue -from threading import Thread +from queue import Queue +from threading import RLock, Thread from typing import Callable, Dict, List, Optional, Set from langfuse._client.environment_variables import ( @@ -18,6 +18,7 @@ ) DEFAULT_PROMPT_CACHE_REFRESH_WORKERS = 1 +_SHUTDOWN_SENTINEL = object() class PromptCacheItem: @@ -46,22 +47,24 @@ def __init__(self, queue: Queue, identifier: int): def run(self) -> None: while self.running: + task = self._queue.get() + + if task is _SHUTDOWN_SENTINEL: + self._queue.task_done() + break + + logger.debug( + f"PromptCacheRefreshConsumer processing task, {self._identifier}" + ) try: - task = self._queue.get(timeout=1) - logger.debug( - f"PromptCacheRefreshConsumer processing task, {self._identifier}" + task() + # Task failed, but we still consider it processed + except Exception as e: + logger.warning( + f"PromptCacheRefreshConsumer encountered an error, cache was not refreshed: {self._identifier}, {e}" ) - try: - task() - # Task failed, but we still consider it processed - except Exception as e: - logger.warning( - f"PromptCacheRefreshConsumer encountered an error, cache was not refreshed: {self._identifier}, {e}" - ) - self._queue.task_done() - except Empty: - pass + self._queue.task_done() def pause(self) -> None: """Pause the consumer.""" @@ -73,12 +76,14 @@ class PromptCacheTaskManager(object): _threads: int _queue: Queue _processing_keys: Set[str] + _lock: RLock def __init__(self, threads: int = 1): self._queue = Queue() self._consumers = [] self._threads = threads self._processing_keys = set() + self._lock = RLock() for i in range(self._threads): consumer = PromptCacheRefreshConsumer(self._queue, i) @@ -88,16 +93,23 @@ def __init__(self, threads: int = 1): atexit.register(self.shutdown) def add_task(self, key: str, task: Callable[[], None]) -> None: - if key not in self._processing_keys: - logger.debug(f"Adding prompt cache refresh task for key: {key}") - self._processing_keys.add(key) - wrapped_task = self._wrap_task(key, task) - self._queue.put((wrapped_task)) - else: - logger.debug(f"Prompt cache refresh task already submitted for key: {key}") + with self._lock: + if key not in self._processing_keys: + logger.debug(f"Adding prompt cache refresh task for key: {key}") + self._processing_keys.add(key) + wrapped_task = self._wrap_task(key, task) + self._queue.put((wrapped_task)) + else: + logger.debug( + f"Prompt cache refresh task already submitted for key: {key}" + ) def active_tasks(self) -> int: - return len(self._processing_keys) + with self._lock: + return len(self._processing_keys) + + def wait_for_idle(self) -> None: + self._queue.join() def _wrap_task(self, key: str, task: Callable[[], None]) -> Callable[[], None]: def wrapped() -> None: @@ -105,7 +117,8 @@ def wrapped() -> None: try: task() finally: - self._processing_keys.remove(key) + with self._lock: + self._processing_keys.remove(key) logger.debug(f"Refreshed prompt cache for key: {key}") return wrapped @@ -120,6 +133,9 @@ def shutdown(self) -> None: for consumer in self._consumers: consumer.pause() + for _ in self._consumers: + self._queue.put(_SHUTDOWN_SENTINEL) + for consumer in self._consumers: try: consumer.join() @@ -132,6 +148,7 @@ def shutdown(self) -> None: class PromptCache: _cache: Dict[str, PromptCacheItem] + _lock: RLock _task_manager: PromptCacheTaskManager """Task manager for refreshing cache""" @@ -140,34 +157,60 @@ def __init__( self, max_prompt_refresh_workers: int = DEFAULT_PROMPT_CACHE_REFRESH_WORKERS ): self._cache = {} + self._lock = RLock() self._task_manager = PromptCacheTaskManager(threads=max_prompt_refresh_workers) logger.debug("Prompt cache initialized.") def get(self, key: str) -> Optional[PromptCacheItem]: - return self._cache.get(key, None) + with self._lock: + return self._cache.get(key, None) def set(self, key: str, value: PromptClient, ttl_seconds: Optional[int]) -> None: if ttl_seconds is None: ttl_seconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS - self._cache[key] = PromptCacheItem(value, ttl_seconds) + with self._lock: + self._cache[key] = PromptCacheItem(value, ttl_seconds) def delete(self, key: str) -> None: - self._cache.pop(key, None) + with self._lock: + self._cache.pop(key, None) def invalidate(self, prompt_name: str) -> None: """Invalidate all cached prompts with the given prompt name.""" - for key in list(self._cache): - if key.startswith(prompt_name): - del self._cache[key] + with self._lock: + for key in list(self._cache): + if key.startswith(prompt_name): + del self._cache[key] def add_refresh_prompt_task(self, key: str, fetch_func: Callable[[], None]) -> None: logger.debug(f"Submitting refresh task for key: {key}") self._task_manager.add_task(key, fetch_func) + def add_refresh_prompt_task_if_current( + self, + key: str, + expected_item: PromptCacheItem, + fetch_func: Callable[[], None], + ) -> None: + with self._lock: + current_item = self._cache.get(key) + if ( + current_item is not None + and current_item is not expected_item + and not current_item.is_expired() + ): + logger.debug( + f"Skipping refresh task for key: {key} because cache is already fresh." + ) + return + + self.add_refresh_prompt_task(key, fetch_func) + def clear(self) -> None: """Clear the entire prompt cache, removing all cached prompts.""" - self._cache.clear() + with self._lock: + self._cache.clear() @staticmethod def generate_cache_key( diff --git a/pyproject.toml b/pyproject.toml index ea7f49f81..68638167c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,12 @@ module-root = "" [tool.pytest.ini_options] log_cli = true +markers = [ + "unit: deterministic tests that run without a Langfuse server", + "e2e: tests that require a real Langfuse server or persisted backend behaviour", + "serial_e2e: e2e tests that must not share server concurrency with the rest of the suite", + "live_provider: tests that call live model providers and run as a dedicated CI suite", +] [tool.mypy] python_version = "3.12" diff --git a/scripts/select_e2e_shard.py b/scripts/select_e2e_shard.py new file mode 100644 index 000000000..688d8468d --- /dev/null +++ b/scripts/select_e2e_shard.py @@ -0,0 +1,114 @@ +import argparse +import ast +import json +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +E2E_ROOT = REPO_ROOT / "tests" / "e2e" + +# These weights keep the existing balance close to the observed runtime split, +# while new files automatically fall back to their local test count. +HISTORICAL_WEIGHTS = { + "tests/e2e/test_batch_evaluation.py": 41, + "tests/e2e/test_core_sdk.py": 53, + "tests/e2e/test_datasets.py": 7, + "tests/e2e/test_decorators.py": 32, + "tests/e2e/test_experiments.py": 17, + "tests/e2e/test_media.py": 1, + "tests/e2e/test_prompt.py": 27, +} + + +def relative_test_path(path: Path) -> str: + return path.relative_to(REPO_ROOT).as_posix() + + +def discover_e2e_files() -> list[Path]: + return sorted(E2E_ROOT.glob("test_*.py")) + + +def count_test_functions(path: Path) -> int: + module = ast.parse(path.read_text(encoding="utf-8")) + return sum( + 1 + for node in module.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name.startswith("test_") + ) + + +def estimate_weight(path: Path) -> int: + try: + relative_path = relative_test_path(path) + except ValueError: + relative_path = None + if relative_path is not None and relative_path in HISTORICAL_WEIGHTS: + return HISTORICAL_WEIGHTS[relative_path] + + return max(count_test_functions(path), 1) + + +def assign_shards( + paths: list[Path], shard_count: int +) -> tuple[list[list[str]], list[int]]: + shard_loads = [0] * shard_count + shards: list[list[str]] = [[] for _ in range(shard_count)] + + weighted_paths = sorted( + ((estimate_weight(path), relative_test_path(path)) for path in paths), + key=lambda item: (-item[0], item[1]), + ) + + for weight, relative_path in weighted_paths: + shard_index = min( + range(shard_count), key=lambda index: (shard_loads[index], index) + ) + shards[shard_index].append(relative_path) + shard_loads[shard_index] += weight + + return [sorted(shard) for shard in shards], shard_loads + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Select the files for one e2e CI shard." + ) + parser.add_argument("--shard-index", required=True, type=int) + parser.add_argument("--shard-count", default=2, type=int) + parser.add_argument("--json", action="store_true") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + + if args.shard_count < 1: + raise SystemExit("--shard-count must be at least 1") + + if args.shard_index < 0 or args.shard_index >= args.shard_count: + raise SystemExit("--shard-index must be within the configured shard count") + + shards, shard_loads = assign_shards(discover_e2e_files(), args.shard_count) + selected_files = shards[args.shard_index] + + if args.json: + print( + json.dumps( + { + "shard_count": args.shard_count, + "shard_index": args.shard_index, + "selected_files": selected_files, + "shard_loads": shard_loads, + } + ) + ) + return 0 + + for path in selected_files: + print(path) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/api_wrapper.py b/tests/api_wrapper.py deleted file mode 100644 index 6067e6bfa..000000000 --- a/tests/api_wrapper.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -from time import sleep - -import httpx - - -class LangfuseAPI: - def __init__(self, username=None, password=None, base_url=None): - username = username if username else os.environ["LANGFUSE_PUBLIC_KEY"] - password = password if password else os.environ["LANGFUSE_SECRET_KEY"] - self.auth = (username, password) - self.BASE_URL = base_url if base_url else os.environ["LANGFUSE_BASE_URL"] - - def get_observation(self, observation_id): - sleep(1) - url = f"{self.BASE_URL}/api/public/observations/{observation_id}" - response = httpx.get(url, auth=self.auth) - return response.json() - - def get_scores(self, page=None, limit=None, user_id=None, name=None): - sleep(1) - params = {"page": page, "limit": limit, "userId": user_id, "name": name} - url = f"{self.BASE_URL}/api/public/scores" - response = httpx.get(url, params=params, auth=self.auth) - return response.json() - - def get_traces(self, page=None, limit=None, user_id=None, name=None): - sleep(1) - params = {"page": page, "limit": limit, "userId": user_id, "name": name} - url = f"{self.BASE_URL}/api/public/traces" - response = httpx.get(url, params=params, auth=self.auth) - return response.json() - - def get_trace(self, trace_id): - sleep(1) - url = f"{self.BASE_URL}/api/public/traces/{trace_id}" - response = httpx.get(url, auth=self.auth) - return response.json() diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..1aea59889 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,167 @@ +import json +from pathlib import Path +from typing import Any, Iterable, Sequence + +import pytest +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import ReadableSpan, TracerProvider +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult + +from langfuse._client.client import Langfuse +from langfuse._client.resource_manager import LangfuseResourceManager + +SERIAL_E2E_NODEIDS = { + "tests/e2e/test_core_sdk.py::test_create_trace", + "tests/e2e/test_core_sdk.py::test_create_boolean_score", + "tests/e2e/test_core_sdk.py::test_create_categorical_score", + "tests/e2e/test_core_sdk.py::test_create_score_with_custom_timestamp", + "tests/e2e/test_decorators.py::test_return_dict_for_output", + "tests/e2e/test_decorators.py::test_media", + "tests/e2e/test_decorators.py::test_merge_metadata_and_tags", + "tests/e2e/test_experiments.py::test_boolean_score_types", + "tests/e2e/test_media.py::test_replace_media_reference_string_in_object", +} + + +class InMemorySpanExporter(SpanExporter): + """Simple in-memory exporter to collect spans for deterministic tests.""" + + def __init__(self) -> None: + self._finished_spans: list[ReadableSpan] = [] + self._stopped = False + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + if self._stopped: + return SpanExportResult.FAILURE + + self._finished_spans.extend(spans) + return SpanExportResult.SUCCESS + + def shutdown(self) -> None: + self._stopped = True + + def get_finished_spans(self) -> list[ReadableSpan]: + return list(self._finished_spans) + + def clear(self) -> None: + self._finished_spans.clear() + + +def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: + for item in items: + test_group = Path(str(item.fspath)).parent.name + + if test_group == "unit": + item.add_marker(pytest.mark.unit) + continue + + if test_group == "e2e": + item.add_marker(pytest.mark.e2e) + if item.nodeid in SERIAL_E2E_NODEIDS: + item.add_marker(pytest.mark.serial_e2e) + continue + + if test_group == "live_provider": + item.add_marker(pytest.mark.e2e) + item.add_marker(pytest.mark.live_provider) + + +@pytest.fixture(autouse=True) +def reset_langfuse_state(request: pytest.FixtureRequest) -> Iterable[None]: + if request.node.get_closest_marker("unit") is None: + yield + return + + LangfuseResourceManager.reset() + yield + LangfuseResourceManager.reset() + + +@pytest.fixture +def memory_exporter() -> Iterable[InMemorySpanExporter]: + exporter = InMemorySpanExporter() + yield exporter + exporter.shutdown() + + +@pytest.fixture +def langfuse_memory_client( + monkeypatch: pytest.MonkeyPatch, memory_exporter: InMemorySpanExporter +) -> Iterable[Langfuse]: + monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "test-public-key") + monkeypatch.setenv("LANGFUSE_SECRET_KEY", "test-secret-key") + monkeypatch.setenv("LANGFUSE_BASE_URL", "http://test-host") + + tracer_provider = TracerProvider(resource=Resource.create({"service.name": "test"})) + + def mock_init(self: Any, **kwargs: Any) -> None: + from opentelemetry.sdk.trace.export import BatchSpanProcessor + + from langfuse._client.span_filter import is_default_export_span + + self.public_key = kwargs.get("public_key", "test-public-key") + blocked_scopes = kwargs.get("blocked_instrumentation_scopes") + self.blocked_instrumentation_scopes = ( + blocked_scopes if blocked_scopes is not None else [] + ) + self._should_export_span = ( + kwargs.get("should_export_span") or is_default_export_span + ) + BatchSpanProcessor.__init__( + self, + span_exporter=memory_exporter, + max_export_batch_size=512, + schedule_delay_millis=1, + ) + + monkeypatch.setattr( + "langfuse._client.span_processor.LangfuseSpanProcessor.__init__", + mock_init, + ) + + client = Langfuse( + public_key="test-public-key", + secret_key="test-secret-key", + base_url="http://test-host", + tracing_enabled=True, + tracer_provider=tracer_provider, + ) + + yield client + client.flush() + + +@pytest.fixture +def get_span(memory_exporter: InMemorySpanExporter): + def _get_span(name: str) -> ReadableSpan: + for span in memory_exporter.get_finished_spans(): + if span.name == name: + return span + + raise AssertionError( + f"Span {name!r} not found in {[span.name for span in memory_exporter.get_finished_spans()]}" + ) + + return _get_span + + +@pytest.fixture +def find_spans(memory_exporter: InMemorySpanExporter): + def _find_spans(name: str) -> list[ReadableSpan]: + return [ + span for span in memory_exporter.get_finished_spans() if span.name == name + ] + + return _find_spans + + +@pytest.fixture +def json_attr(): + def _json_attr(span: ReadableSpan, attribute: str) -> Any: + value = span.attributes[attribute] + if not isinstance(value, str): + return value + + return json.loads(value) + + return _json_attr diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/e2e/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/test_batch_evaluation.py b/tests/e2e/test_batch_evaluation.py similarity index 96% rename from tests/test_batch_evaluation.py rename to tests/e2e/test_batch_evaluation.py index 27c49acdd..0632b21b8 100644 --- a/tests/test_batch_evaluation.py +++ b/tests/e2e/test_batch_evaluation.py @@ -18,7 +18,7 @@ EvaluatorStats, ) from langfuse.experiment import Evaluation -from tests.utils import create_uuid +from tests.support.utils import create_uuid, get_api, wait_for_result # ============================================================================ # FIXTURES & SETUP @@ -40,6 +40,40 @@ def sample_trace_name(): return f"batch-eval-test-{create_uuid()}" +def _seed_trace_corpus( + *, trace_count: int = 6, tag: str | None = None +) -> tuple[str, list[str]]: + langfuse_client = get_client() + corpus_tag = tag or f"batch-eval-seed-{create_uuid()}" + trace_names: list[str] = [] + + for index in range(trace_count): + trace_name = f"{corpus_tag}-trace-{index}" + trace_names.append(trace_name) + with langfuse_client.start_as_current_observation(name=trace_name) as span: + with propagate_attributes(tags=[corpus_tag]): + span.set_trace_io( + input=f"Seed input {index}", + output=f"Seed output {index}", + ) + + langfuse_client.flush() + + filter_json = f'[{{"type": "arrayOptions", "column": "tags", "operator": "any of", "value": ["{corpus_tag}"]}}]' + api = get_api(retry=False) + wait_for_result( + lambda: api.trace.list(filter=filter_json, limit=trace_count), + is_result_ready=lambda response: len(response.data) >= trace_count, + ) + + return corpus_tag, trace_names + + +@pytest.fixture(scope="module", autouse=True) +def seeded_batch_evaluation_traces(): + _seed_trace_corpus() + + def simple_trace_mapper(*, item): """Simple mapper for traces.""" return EvaluatorInputs( diff --git a/tests/test_core_sdk.py b/tests/e2e/test_core_sdk.py similarity index 94% rename from tests/test_core_sdk.py rename to tests/e2e/test_core_sdk.py index b873e3206..9b491a5aa 100644 --- a/tests/test_core_sdk.py +++ b/tests/e2e/test_core_sdk.py @@ -10,10 +10,12 @@ from langfuse import Langfuse, propagate_attributes from langfuse._client.resource_manager import LangfuseResourceManager from langfuse._utils import _get_timestamp -from tests.api_wrapper import LangfuseAPI -from tests.utils import ( +from tests.support.api_wrapper import LangfuseAPI +from tests.support.utils import ( create_uuid, get_api, + wait_for_result, + wait_for_trace, ) @@ -153,7 +155,7 @@ def test_create_session_score(): sleep(2) # Retrieve and verify - score = langfuse.api.scores.get_by_id(score_id) + score = get_api().scores.get_by_id(score_id) # find the score by name (server may transform the id format) assert score is not None @@ -229,7 +231,7 @@ def test_create_boolean_score(): # Ensure data is sent langfuse.flush() - sleep(2) + api_wrapper.get_trace(trace_id) # Create a boolean score score_id = create_uuid() @@ -252,10 +254,14 @@ def test_create_boolean_score(): # Ensure data is sent langfuse.flush() - sleep(2) # Retrieve and verify - trace = api_wrapper.get_trace(trace_id) + trace = api_wrapper.get_trace( + trace_id, + is_result_ready=lambda trace: any( + score["name"] == "this-is-a-score" for score in trace.get("scores", []) + ), + ) # Find the score we created by name created_score = next( @@ -284,7 +290,7 @@ def test_create_categorical_score(): # Ensure data is sent langfuse.flush() - sleep(2) + api_wrapper.get_trace(trace_id) # Create a categorical score score_id = create_uuid() @@ -306,10 +312,14 @@ def test_create_categorical_score(): # Ensure data is sent langfuse.flush() - sleep(2) # Retrieve and verify - trace = api_wrapper.get_trace(trace_id) + trace = api_wrapper.get_trace( + trace_id, + is_result_ready=lambda trace: any( + score["name"] == "this-is-a-score" for score in trace.get("scores", []) + ), + ) # Find the score we created by name created_score = next( @@ -399,7 +409,7 @@ def test_create_score_with_custom_timestamp(): # Ensure data is sent langfuse.flush() - sleep(2) + api_wrapper.get_trace(trace_id) custom_timestamp = datetime.now(timezone.utc) - timedelta(hours=1) score_id = create_uuid() @@ -414,10 +424,15 @@ def test_create_score_with_custom_timestamp(): # Ensure data is sent langfuse.flush() - sleep(2) # Retrieve and verify - trace = api_wrapper.get_trace(trace_id) + trace = api_wrapper.get_trace( + trace_id, + is_result_ready=lambda trace: any( + score["name"] == "custom-timestamp-score" + for score in trace.get("scores", []) + ), + ) # Find the score we created by name created_score = next( @@ -460,10 +475,18 @@ def test_create_trace(): # Ensure data is sent to the API langfuse.flush() - sleep(2) # Retrieve the trace from the API - trace = LangfuseAPI().get_trace(trace_id) + trace = LangfuseAPI().get_trace( + trace_id, + is_result_ready=lambda trace: ( + trace.get("name") == trace_name + and trace.get("userId") == "test" + and trace.get("metadata", {}).get("key") == "value" + and trace.get("tags") == ["tag1", "tag2"] + and trace.get("public") is True + ), + ) # Verify all trace properties assert trace["name"] == trace_name @@ -499,11 +522,20 @@ def test_create_update_trace(): # Ensure data is sent to the API langfuse.flush() - sleep(2) assert isinstance(trace_id, str) # Retrieve and verify trace - trace = get_api().trace.get(trace_id) + trace = wait_for_trace( + trace_id, + is_result_ready=lambda trace: ( + trace.name == trace_name + and trace.user_id == "test" + and trace.metadata is not None + and trace.metadata.get("key") == "value" + and trace.metadata.get("key2") == "value2" + and trace.public is True + ), + ) assert trace.name == trace_name assert trace.user_id == "test" @@ -1260,10 +1292,14 @@ def test_end_generation(): # Ensure data is sent langfuse.flush() - sleep(2) # Retrieve and verify - trace = api_wrapper.get_trace(trace_id) + trace = api_wrapper.get_trace( + trace_id, + is_result_ready=lambda trace: any( + obs["name"] == "query-generation" for obs in trace.get("observations", []) + ), + ) # Find generation by name generations = [ @@ -1797,16 +1833,20 @@ def test_fetch_traces(): # Ensure data is sent langfuse.flush() - sleep(3) - # Fetch all traces with the same name - # Note: Using session_id in the query is causing a server error, - # but we keep the session_id in the trace data to ensure it's being stored correctly - all_traces = get_api().trace.list(name=name, limit=10) + expected_trace_ids = set(trace_ids) + api = get_api(retry=False) + + # Fetch all traces with the same name. + all_traces = wait_for_result( + lambda: api.trace.list(name=name, limit=10), + is_result_ready=lambda response: ( + {trace.id for trace in response.data} == expected_trace_ids + ), + ) # Verify we got all traces assert len(all_traces.data) == 3 - assert all_traces.meta.total_items == 3 # Verify trace properties for trace in all_traces.data: @@ -1815,11 +1855,19 @@ def test_fetch_traces(): assert trace.input == {"key": "value"} assert trace.output == "output-value" - # Test pagination by fetching just one trace - paginated_response = get_api().trace.list(name=name, limit=1, page=2) - assert len(paginated_response.data) == 1 - assert paginated_response.meta.total_items == 3 - assert paginated_response.meta.total_pages == 3 + # Test pagination by fetching the first three pages one at a time and + # confirming they collectively cover the created traces. + paginated_ids = set() + for page in range(1, 4): + paginated_response = wait_for_result( + lambda page=page: api.trace.list(name=name, limit=1, page=page), + is_result_ready=lambda response: ( + len(response.data) == 1 and response.data[0].id in expected_trace_ids + ), + ) + paginated_ids.add(paginated_response.data[0].id) + + assert paginated_ids == expected_trace_ids def test_get_observation(): @@ -1874,10 +1922,16 @@ def test_get_observations(): # Ensure data is sent langfuse.flush() - sleep(2) + api = get_api(retry=False) # Fetch observations using the API - observations = get_api().legacy.observations_v1.get_many(name=name, limit=10) + expected_generation_ids = {gen1_id, gen2_id} + observations = wait_for_result( + lambda: api.legacy.observations_v1.get_many(name=name, limit=10), + is_result_ready=lambda response: expected_generation_ids.issubset( + {obs.id for obs in response.data} + ), + ) # Verify fetched observations assert len(observations.data) == 2 @@ -1891,30 +1945,39 @@ def test_get_observations(): assert gen1_id in gen_ids assert gen2_id in gen_ids - # Test pagination - paginated_response = get_api().legacy.observations_v1.get_many( - name=name, limit=1, page=2 - ) - assert len(paginated_response.data) == 1 - assert paginated_response.meta.total_items == 2 # Parent span + 2 generations - assert paginated_response.meta.total_pages == 2 + # Test pagination by confirming both created generations can be reached + # across separate pages. + paginated_ids = set() + for page in range(1, 3): + paginated_response = wait_for_result( + lambda page=page: api.legacy.observations_v1.get_many( + name=name, limit=1, page=page + ), + is_result_ready=lambda response: ( + len(response.data) == 1 + and response.data[0].id in expected_generation_ids + ), + ) + paginated_ids.add(paginated_response.data[0].id) + + assert paginated_ids == expected_generation_ids def test_get_trace_not_found(): # Attempt to fetch a non-existent trace using the API with pytest.raises(Exception): - get_api().trace.get(create_uuid()) + get_api(retry=False).trace.get(create_uuid()) def test_get_observation_not_found(): # Attempt to fetch a non-existent observation using the API with pytest.raises(Exception): - get_api().legacy.observations_v1.get(create_uuid()) + get_api(retry=False).legacy.observations_v1.get(create_uuid()) def test_get_traces_empty(): # Fetch traces with a filter that should return no results - response = get_api().trace.list(name=create_uuid()) + response = get_api(retry=False).trace.list(name=create_uuid()) assert len(response.data) == 0 assert response.meta.total_items == 0 @@ -1922,7 +1985,7 @@ def test_get_traces_empty(): def test_get_observations_empty(): # Fetch observations with a filter that should return no results - response = get_api().legacy.observations_v1.get_many(name=create_uuid()) + response = get_api(retry=False).legacy.observations_v1.get_many(name=create_uuid()) assert len(response.data) == 0 assert response.meta.total_items == 0 diff --git a/tests/test_datasets.py b/tests/e2e/test_datasets.py similarity index 84% rename from tests/test_datasets.py rename to tests/e2e/test_datasets.py index 209a0d19f..8d575180a 100644 --- a/tests/test_datasets.py +++ b/tests/e2e/test_datasets.py @@ -3,7 +3,7 @@ from langfuse import Langfuse from langfuse.api import DatasetStatus -from tests.utils import create_uuid +from tests.support.utils import create_uuid, wait_for_result def test_create_and_get_dataset(): @@ -97,20 +97,38 @@ def test_upsert_and_get_dataset_item(): dataset_name=name, input=input, expected_output=input ) - # Instead, get all dataset items and find the one with matching ID - dataset = langfuse.get_dataset(name) - get_item = None - for i in dataset.items: - if i.id == item.id: - get_item = i - break + get_item = wait_for_result( + lambda: langfuse.api.dataset_items.get(item.id), + is_result_ready=lambda dataset_item: dataset_item.id == item.id, + ) - assert get_item is not None assert get_item.input == input assert get_item.id == item.id assert get_item.expected_output == input new_input = {"input": "Hello World 2"} + langfuse.create_dataset_item( + dataset_name=name, + input=new_input, + id=item.id, + expected_output=new_input, + ) + + get_new_item = wait_for_result( + lambda: langfuse.api.dataset_items.get(item.id), + is_result_ready=lambda dataset_item: ( + dataset_item.id == item.id + and dataset_item.input == new_input + and dataset_item.expected_output == new_input + and dataset_item.status == DatasetStatus.ACTIVE + ), + ) + + assert get_new_item.input == new_input + assert get_new_item.id == item.id + assert get_new_item.expected_output == new_input + assert get_new_item.status == DatasetStatus.ACTIVE + langfuse.create_dataset_item( dataset_name=name, input=new_input, @@ -119,19 +137,29 @@ def test_upsert_and_get_dataset_item(): status=DatasetStatus.ARCHIVED, ) - # Refresh dataset and find updated item - archived_item = langfuse.api.dataset_items.get(id=item.id) + latest_dataset = wait_for_result( + lambda: langfuse.get_dataset(name), + is_result_ready=lambda dataset: all( + dataset_item.id != item.id for dataset_item in dataset.items + ), + ) + + assert all(dataset_item.id != item.id for dataset_item in latest_dataset.items) - assert archived_item is not None + archived_item = wait_for_result( + lambda: langfuse.api.dataset_items.get(item.id), + is_result_ready=lambda dataset_item: ( + dataset_item.id == item.id + and dataset_item.input == new_input + and dataset_item.expected_output == new_input + and dataset_item.status == DatasetStatus.ARCHIVED + ), + ) assert archived_item.input == new_input assert archived_item.id == item.id assert archived_item.expected_output == new_input assert archived_item.status == DatasetStatus.ARCHIVED - # List endpoint does not contain archived items - dataset = langfuse.get_dataset(name) - assert all(i.id != item.id for i in dataset.items) - def test_run_experiment(): """Test running an experiment on a dataset using run_experiment().""" diff --git a/tests/test_decorators.py b/tests/e2e/test_decorators.py similarity index 79% rename from tests/test_decorators.py rename to tests/e2e/test_decorators.py index c6ed42594..7c289980d 100644 --- a/tests/test_decorators.py +++ b/tests/e2e/test_decorators.py @@ -16,7 +16,7 @@ from langfuse._client.resource_manager import LangfuseResourceManager from langfuse.langchain import CallbackHandler from langfuse.media import LangfuseMedia -from tests.utils import get_api +from tests.support.utils import get_api, wait_for_trace mock_metadata = {"key": "metadata"} mock_deep_metadata = {"key": "mock_deep_metadata"} @@ -32,6 +32,32 @@ def removeMockResourceManagerInstances(): LangfuseResourceManager._instances.pop(public_key) +def _get_observation_by_name(trace_data, name): + return next( + observation + for observation in trace_data.observations + if observation.name == name + ) + + +def _is_descendant(trace_data, child_id, ancestor_id): + observations_by_id = { + observation.id: observation for observation in trace_data.observations + } + current_id = child_id + + while current_id in observations_by_id: + current = observations_by_id[current_id] + parent_id = current.parent_observation_id + if parent_id == ancestor_id: + return True + if parent_id not in observations_by_id: + return False + current_id = parent_id + + return False + + def test_nested_observations(): mock_name = "test_nested_observations" langfuse = get_client() @@ -402,39 +428,44 @@ def level_1_function(*args, **kwargs): langfuse.flush() - trace_data = get_api().trace.get(mock_trace_id) + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.session_id == mock_session_id + and trace.name == mock_name + and { + "level_1_function", + "level_2_function", + "level_3_function", + "langchain_operations", + "ChatPromptTemplate", + }.issubset({observation.name for observation in trace.observations}) + ), + ) assert len(trace_data.observations) > 2 - # Check correct nesting - adjacencies = defaultdict(list) - for o in trace_data.observations: - adjacencies[o.parent_observation_id].append(o) - - assert len(adjacencies) > 2 - # trace parameters if set anywhere in the call stack assert trace_data.session_id == mock_session_id assert trace_data.name == mock_name - # Check that the langchain_operations is at the correct level - level_1_observation = next( - o - for o in trace_data.observations - if o.parent_observation_id not in [o.id for o in trace_data.observations] - ) - level_2_observation = adjacencies[level_1_observation.id][0] - level_3_observation = adjacencies[level_2_observation.id][0] - langchain_observation = adjacencies[level_3_observation.id][0] + level_1_observation = _get_observation_by_name(trace_data, "level_1_function") + level_2_observation = _get_observation_by_name(trace_data, "level_2_function") + level_3_observation = _get_observation_by_name(trace_data, "level_3_function") + langchain_observation = _get_observation_by_name(trace_data, "langchain_operations") + prompt_observation = _get_observation_by_name(trace_data, "ChatPromptTemplate") assert level_1_observation.name == "level_1_function" + assert _is_descendant(trace_data, level_2_observation.id, level_1_observation.id) assert level_2_observation.name == "level_2_function" assert level_2_observation.metadata["key"] == mock_metadata["key"] + assert _is_descendant(trace_data, level_3_observation.id, level_2_observation.id) assert level_3_observation.name == "level_3_function" assert level_3_observation.metadata["key"] == mock_deep_metadata["key"] + assert _is_descendant(trace_data, langchain_observation.id, level_3_observation.id) assert langchain_observation.name == "langchain_operations" # Check that LangChain components are captured - assert any([o.name == "ChatPromptTemplate" for o in trace_data.observations]) + assert _is_descendant(trace_data, prompt_observation.id, langchain_observation.id) def test_get_current_trace_url(): @@ -488,26 +519,37 @@ def level_1_function(*args, **kwargs): *mock_args, **mock_kwargs, langfuse_trace_id=mock_trace_id ) langfuse.flush() - sleep(1) assert result == "level_3" # Wrapped function returns correctly # ID setting for span or trace - trace_data = get_api().trace.get(mock_trace_id) + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: { + "test-observation-score", + "test-trace-score", + "another-test-trace-score", + }.issubset({score.name for score in trace.scores}), + ) assert ( len(trace_data.observations) == 3 ) # Top-most function is trace, so it's not an observations assert trace_data.name == mock_name # Check for correct scoring - scores = trace_data.scores + scores_by_name = defaultdict(list) + for score in trace_data.scores: + scores_by_name[score.name].append(score) - assert len(scores) == 3 + assert len(scores_by_name["test-trace-score"]) == 1 + assert len(scores_by_name["another-test-trace-score"]) == 1 + assert len(scores_by_name["test-observation-score"]) == 1 trace_scores = [ - s for s in scores if s.trace_id == mock_trace_id and s.observation_id is None + scores_by_name["test-trace-score"][0], + scores_by_name["another-test-trace-score"][0], ] - observation_score = [s for s in scores if s.observation_id is not None][0] + observation_score = scores_by_name["test-observation-score"][0] assert any( [ @@ -861,27 +903,29 @@ async def level_1_function(*args, **kwargs): assert result == "level_1" # Wrapped function returns correctly # ID setting for span or trace - trace_data = get_api().trace.get(mock_trace_id) - assert len(trace_data.observations) == 3 + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.session_id == mock_session_id + and trace.name == mock_name + and { + "level_1_function", + "level_2_function", + "OpenAI-generation", + }.issubset({observation.name for observation in trace.observations}) + ), + ) + assert len(trace_data.observations) >= 3 # trace parameters if set anywhere in the call stack assert trace_data.session_id == mock_session_id assert trace_data.name == mock_name - # Check correct nesting - adjacencies = defaultdict(list) - for o in trace_data.observations: - adjacencies[o.parent_observation_id or o.trace_id].append(o) - - assert len(adjacencies) == 3 - - level_1_observation = next( - o - for o in trace_data.observations - if o.parent_observation_id not in [o.id for o in trace_data.observations] - ) - level_2_observation = adjacencies[level_1_observation.id][0] - level_3_observation = adjacencies[level_2_observation.id][0] + level_1_observation = _get_observation_by_name(trace_data, "level_1_function") + level_2_observation = _get_observation_by_name(trace_data, "level_2_function") + level_3_observation = _get_observation_by_name(trace_data, "OpenAI-generation") + assert _is_descendant(trace_data, level_2_observation.id, level_1_observation.id) + assert _is_descendant(trace_data, level_3_observation.id, level_2_observation.id) assert level_2_observation.metadata["key"] == mock_metadata["key"] @@ -1008,8 +1052,14 @@ def function(): assert result == mock_output - trace_data = get_api().trace.get(mock_trace_id) - assert trace_data.observations[0].output == mock_output + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: any( + observation.name == "function" and observation.output == mock_output + for observation in trace.observations + ), + ) + assert _get_observation_by_name(trace_data, "function").output == mock_output def test_media(): @@ -1049,7 +1099,21 @@ def main(): langfuse.flush() - trace_data = get_api().trace.get(mock_trace_id) + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + "@@@langfuseMedia:type=application/pdf|id=" + in (trace.input or {}).get("context", {}).get("nested", "") + and "@@@langfuseMedia:type=application/pdf|id=" + in (trace.output or {}).get("context", {}).get("nested", "") + and any( + "@@@langfuseMedia:type=application/pdf|id=" + in observation.metadata.get("context", {}).get("nested", "") + for observation in trace.observations + if observation.metadata + ) + ), + ) assert ( "@@@langfuseMedia:type=application/pdf|id=" @@ -1091,7 +1155,15 @@ def main(): langfuse.flush() - trace_data = get_api().trace.get(mock_trace_id) + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.metadata is not None + and trace.metadata.get("key1") == "value1" + and trace.metadata.get("key2") == "value2" + and trace.tags == ["tag1", "tag2"] + ), + ) assert trace_data.metadata["key1"] == "value1" assert trace_data.metadata["key2"] == "value2" @@ -1105,124 +1177,141 @@ def test_multiproject_context_propagation_basic(): client1 = Langfuse() # Reads from environment Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") - # Verify both instances are registered - assert len(LangfuseResourceManager._instances) == 2 + try: + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 - mock_name = "test_multiproject_context_propagation_basic" - # Use known public key from environment - env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] - # In multi-project setup, must specify which client to use - langfuse = get_client(public_key=env_public_key) - mock_trace_id = langfuse.create_trace_id() + mock_name = "test_multiproject_context_propagation_basic" + # Use known public key from environment + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + # In multi-project setup, must specify which client to use + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() - @observe(as_type="generation", capture_output=False) - def level_3_function(): - # This function should inherit the public key from level_1_function - # and NOT need langfuse_public_key parameter - langfuse_client = get_client() - langfuse_client.update_current_generation(metadata={"level": "3"}) - with propagate_attributes(trace_name=mock_name): - pass - return "level_3" - - @observe() - def level_2_function(): - # This function should also inherit the public key - level_3_function() - langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "2"}) - return "level_2" + @observe(as_type="generation", capture_output=False) + def level_3_function(): + # This function should inherit the public key from level_1_function + # and NOT need langfuse_public_key parameter + langfuse_client = get_client() + langfuse_client.update_current_generation(metadata={"level": "3"}) + with propagate_attributes(trace_name=mock_name): + pass + return "level_3" - @observe() - def level_1_function(*args, **kwargs): - # Only this top-level function receives langfuse_public_key - level_2_function() - langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "1"}) - return "level_1" + @observe() + def level_2_function(): + # This function should also inherit the public key + level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2"}) + return "level_2" - result = level_1_function( - *mock_args, - **mock_kwargs, - langfuse_trace_id=mock_trace_id, - langfuse_public_key=env_public_key, # Only provided to top-level function - ) + @observe() + def level_1_function(*args, **kwargs): + # Only this top-level function receives langfuse_public_key + level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1"}) + return "level_1" - # Use the correct client for flushing - client1.flush() + result = level_1_function( + *mock_args, + **mock_kwargs, + langfuse_trace_id=mock_trace_id, + langfuse_public_key=env_public_key, # Only provided to top-level function + ) - assert result == "level_1" + # Use the correct client for flushing + client1.flush() - # Verify trace was created properly - trace_data = get_api().trace.get(mock_trace_id) - assert len(trace_data.observations) == 3 - assert trace_data.name == mock_name + assert result == "level_1" - # Reset instances to not leak to other test suites - removeMockResourceManagerInstances() + # Verify trace was created properly + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.name == mock_name and len(trace.observations) == 3 + ), + ) + assert len(trace_data.observations) == 3 + assert trace_data.name == mock_name + finally: + removeMockResourceManagerInstances() def test_multiproject_context_propagation_deep_nesting(): client1 = Langfuse() # Reads from environment Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") - # Verify both instances are registered - assert len(LangfuseResourceManager._instances) == 2 - - mock_name = "test_multiproject_context_propagation_deep_nesting" - env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] - langfuse = get_client(public_key=env_public_key) - mock_trace_id = langfuse.create_trace_id() - - @observe(as_type="generation") - def level_4_function(): - langfuse_client = get_client() - langfuse_client.update_current_generation(metadata={"level": "4"}) - return "level_4" + try: + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 - @observe() - def level_3_function(): - result = level_4_function() - langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "3"}) - return result + mock_name = "test_multiproject_context_propagation_deep_nesting" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() - @observe() - def level_2_function(): - result = level_3_function() - langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "2"}) - return result + @observe(as_type="generation") + def level_4_function(): + langfuse_client = get_client() + langfuse_client.update_current_generation(metadata={"level": "4"}) + return "level_4" - @observe() - def level_1_function(*args, **kwargs): - with propagate_attributes(trace_name=mock_name): - result = level_2_function() + @observe() + def level_3_function(): + result = level_4_function() langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "1"}) + langfuse_client.update_current_span(metadata={"level": "3"}) return result - result = level_1_function( - langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key - ) - client1.flush() - - assert result == "level_4" - - trace_data = get_api().trace.get(mock_trace_id) - assert len(trace_data.observations) == 4 - assert trace_data.name == mock_name + @observe() + def level_2_function(): + result = level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2"}) + return result - # Verify all levels were captured - levels = [ - str(obs.metadata.get("level")) - for obs in trace_data.observations - if obs.metadata - ] - assert set(levels) == {"1", "2", "3", "4"} + @observe() + def level_1_function(*args, **kwargs): + with propagate_attributes(trace_name=mock_name): + result = level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1"}) + return result + + result = level_1_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=env_public_key + ) + client1.flush() + + assert result == "level_4" + + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.name == mock_name + and len(trace.observations) == 4 + and {"1", "2", "3", "4"} + == { + str(observation.metadata.get("level")) + for observation in trace.observations + if observation.metadata + } + ), + ) + assert len(trace_data.observations) == 4 + assert trace_data.name == mock_name - # Reset instances to not leak to other test suites - removeMockResourceManagerInstances() + # Verify all levels were captured + levels = [ + str(obs.metadata.get("level")) + for obs in trace_data.observations + if obs.metadata + ] + assert set(levels) == {"1", "2", "3", "4"} + finally: + removeMockResourceManagerInstances() def test_multiproject_context_propagation_override(): @@ -1230,52 +1319,59 @@ def test_multiproject_context_propagation_override(): client1 = Langfuse() # Reads from environment client2 = Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") - # Verify both instances are registered - assert len(LangfuseResourceManager._instances) == 2 - - mock_name = "test_multiproject_context_propagation_override" - env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] - langfuse = get_client(public_key=env_public_key) - mock_trace_id = langfuse.create_trace_id() - - primary_public_key = env_public_key - override_public_key = "pk-test-project2" - - @observe(as_type="generation") - def level_3_function(): - # This function explicitly overrides the inherited public key - langfuse_client = get_client(public_key=override_public_key) - langfuse_client.update_current_generation(metadata={"used_override": "true"}) - return "level_3" - - @observe() - def level_2_function(): - # This function should use the overridden key when calling level_3 - level_3_function(langfuse_public_key=override_public_key) - langfuse_client = get_client(public_key=primary_public_key) - langfuse_client.update_current_span(metadata={"level": "2"}) - return "level_2" + try: + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 + + mock_name = "test_multiproject_context_propagation_override" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() + + primary_public_key = env_public_key + override_public_key = "pk-test-project2" + + @observe(as_type="generation") + def level_3_function(): + # This function explicitly overrides the inherited public key + langfuse_client = get_client(public_key=override_public_key) + langfuse_client.update_current_generation( + metadata={"used_override": "true"} + ) + return "level_3" - @observe() - def level_1_function(*args, **kwargs): - with propagate_attributes(trace_name=mock_name): - level_2_function() - return "level_1" + @observe() + def level_2_function(): + # This function should use the overridden key when calling level_3 + level_3_function(langfuse_public_key=override_public_key) + langfuse_client = get_client(public_key=primary_public_key) + langfuse_client.update_current_span(metadata={"level": "2"}) + return "level_2" - result = level_1_function( - langfuse_trace_id=mock_trace_id, langfuse_public_key=primary_public_key - ) - client1.flush() - client2.flush() + @observe() + def level_1_function(*args, **kwargs): + with propagate_attributes(trace_name=mock_name): + level_2_function() + return "level_1" - assert result == "level_1" + result = level_1_function( + langfuse_trace_id=mock_trace_id, langfuse_public_key=primary_public_key + ) + client1.flush() + client2.flush() - trace_data = get_api().trace.get(mock_trace_id) - assert len(trace_data.observations) == 2 - assert trace_data.name == mock_name + assert result == "level_1" - # Reset instances to not leak to other test suites - removeMockResourceManagerInstances() + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.name == mock_name and len(trace.observations) == 2 + ), + ) + assert len(trace_data.observations) == 2 + assert trace_data.name == mock_name + finally: + removeMockResourceManagerInstances() def test_multiproject_context_propagation_no_public_key(): @@ -1339,68 +1435,79 @@ async def test_multiproject_async_context_propagation_basic(): client1 = Langfuse() # Reads from environment Langfuse(public_key="pk-test-project2", secret_key="sk-test-project2") - # Verify both instances are registered - assert len(LangfuseResourceManager._instances) == 2 - - mock_name = "test_multiproject_async_context_propagation_basic" - env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] - langfuse = get_client(public_key=env_public_key) - mock_trace_id = langfuse.create_trace_id() - - @observe(as_type="generation", capture_output=False) - async def async_level_3_function(): - # This function should inherit the public key from level_1_function - # and NOT need langfuse_public_key parameter - await asyncio.sleep(0.01) # Simulate async work - langfuse_client = get_client() - langfuse_client.update_current_generation( - metadata={"level": "3", "async": True} - ) - with propagate_attributes(trace_name=mock_name): - pass - return "async_level_3" - - @observe() - async def async_level_2_function(): - # This function should also inherit the public key - result = await async_level_3_function() - langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "2", "async": True}) - return result + try: + # Verify both instances are registered + assert len(LangfuseResourceManager._instances) == 2 - @observe() - async def async_level_1_function(*args, **kwargs): - # Only this top-level function receives langfuse_public_key - result = await async_level_2_function() - langfuse_client = get_client() - langfuse_client.update_current_span(metadata={"level": "1", "async": True}) - return result + mock_name = "test_multiproject_async_context_propagation_basic" + env_public_key = os.environ[LANGFUSE_PUBLIC_KEY] + langfuse = get_client(public_key=env_public_key) + mock_trace_id = langfuse.create_trace_id() - result = await async_level_1_function( - *mock_args, - **mock_kwargs, - langfuse_trace_id=mock_trace_id, - langfuse_public_key=env_public_key, # Only provided to top-level function - ) + @observe(as_type="generation", capture_output=False) + async def async_level_3_function(): + # This function should inherit the public key from level_1_function + # and NOT need langfuse_public_key parameter + await asyncio.sleep(0.01) # Simulate async work + langfuse_client = get_client() + langfuse_client.update_current_generation( + metadata={"level": "3", "async": True} + ) + with propagate_attributes(trace_name=mock_name): + pass + return "async_level_3" - # Use the correct client for flushing - client1.flush() + @observe() + async def async_level_2_function(): + # This function should also inherit the public key + result = await async_level_3_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "2", "async": True}) + return result - assert result == "async_level_3" + @observe() + async def async_level_1_function(*args, **kwargs): + # Only this top-level function receives langfuse_public_key + result = await async_level_2_function() + langfuse_client = get_client() + langfuse_client.update_current_span(metadata={"level": "1", "async": True}) + return result - # Verify trace was created properly - trace_data = get_api().trace.get(mock_trace_id) - assert len(trace_data.observations) == 3 - assert trace_data.name == mock_name + result = await async_level_1_function( + *mock_args, + **mock_kwargs, + langfuse_trace_id=mock_trace_id, + langfuse_public_key=env_public_key, # Only provided to top-level function + ) - # Verify all observations have async metadata - async_flags = [ - obs.metadata.get("async") for obs in trace_data.observations if obs.metadata - ] - assert all(async_flags) + # Use the correct client for flushing + client1.flush() + + assert result == "async_level_3" + + # Verify trace was created properly + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + trace.name == mock_name + and len(trace.observations) == 3 + and all( + observation.metadata.get("async") + for observation in trace.observations + if observation.metadata + ) + ), + ) + assert len(trace_data.observations) == 3 + assert trace_data.name == mock_name - # Reset instances to not leak to other test suites - removeMockResourceManagerInstances() + # Verify all observations have async metadata + async_flags = [ + obs.metadata.get("async") for obs in trace_data.observations if obs.metadata + ] + assert all(async_flags) + finally: + removeMockResourceManagerInstances() @pytest.mark.asyncio @@ -1744,7 +1851,19 @@ def root_function(): ) # Verify trace structure - trace_data = get_api().trace.get(mock_trace_id) + trace_data = wait_for_trace( + mock_trace_id, + is_result_ready=lambda trace: ( + len(trace.observations) >= 2 + and {"parent_root", "child_stream"}.issubset( + { + observation.name + for observation in trace.observations + if observation.name + } + ) + ), + ) assert len(trace_data.observations) == 2 # Verify both observations are present diff --git a/tests/test_experiments.py b/tests/e2e/test_experiments.py similarity index 99% rename from tests/test_experiments.py rename to tests/e2e/test_experiments.py index 3ba8b4afa..5f18d9c0b 100644 --- a/tests/test_experiments.py +++ b/tests/e2e/test_experiments.py @@ -12,7 +12,7 @@ ExperimentItem, ExperimentItemResult, ) -from tests.utils import create_uuid, get_api +from tests.support.utils import create_uuid, get_api, wait_for_trace @pytest.fixture @@ -786,13 +786,15 @@ def mock_task_with_boolean_results(*, item: ExperimentItem, **kwargs): time.sleep(3) # Verify scores are persisted via API with correct data types - api = get_api() for i, item_result in enumerate(result.item_results): trace_id = item_result.trace_id assert trace_id is not None, f"Item {i} should have a trace_id" # Fetch trace from API to verify score persistence - trace = api.trace.get(trace_id) + trace = wait_for_trace( + trace_id, + is_result_ready=lambda trace: len(trace.scores) > 0, + ) assert trace is not None, f"Trace {trace_id} should exist" for score in trace.scores: diff --git a/tests/e2e/test_media.py b/tests/e2e/test_media.py new file mode 100644 index 000000000..d322e1788 --- /dev/null +++ b/tests/e2e/test_media.py @@ -0,0 +1,75 @@ +import base64 +import re +from uuid import uuid4 + +from langfuse._client.client import Langfuse +from langfuse.media import LangfuseMedia +from tests.support.utils import wait_for_trace + + +def test_replace_media_reference_string_in_object(): + audio_file = "static/joke_prompt.wav" + with open(audio_file, "rb") as f: + mock_audio_bytes = f.read() + + langfuse = Langfuse() + + mock_trace_name = f"test-trace-with-audio-{uuid4()}" + base64_audio = base64.b64encode(mock_audio_bytes).decode() + + span = langfuse.start_observation( + name=mock_trace_name, + metadata={ + "context": { + "nested": LangfuseMedia( + base64_data_uri=f"data:audio/wav;base64,{base64_audio}" + ) + } + }, + ).end() + + langfuse.flush() + + fetched_trace = wait_for_trace( + span.trace_id, + is_result_ready=lambda trace: ( + bool(trace.observations) + and re.match( + r"^@@@langfuseMedia:type=audio/wav\|id=.+\|source=base64_data_uri@@@$", + trace.observations[0].metadata.get("context", {}).get("nested", ""), + ) + is not None + ), + ) + media_ref = fetched_trace.observations[0].metadata["context"]["nested"] + assert re.match( + r"^@@@langfuseMedia:type=audio/wav\|id=.+\|source=base64_data_uri@@@$", + media_ref, + ) + + resolved_obs = langfuse.resolve_media_references( + obj=fetched_trace.observations[0], resolve_with="base64_data_uri" + ) + + expected_base64 = f"data:audio/wav;base64,{base64_audio}" + assert resolved_obs["metadata"]["context"]["nested"] == expected_base64 + + span2 = langfuse.start_observation( + name=f"2-{mock_trace_name}", + metadata={"context": {"nested": resolved_obs["metadata"]["context"]["nested"]}}, + ).end() + + langfuse.flush() + + fetched_trace2 = wait_for_trace( + span2.trace_id, + is_result_ready=lambda trace: ( + bool(trace.observations) + and trace.observations[0].metadata.get("context", {}).get("nested") + == fetched_trace.observations[0].metadata["context"]["nested"] + ), + ) + assert ( + fetched_trace2.observations[0].metadata["context"]["nested"] + == fetched_trace.observations[0].metadata["context"]["nested"] + ) diff --git a/tests/e2e/test_prompt.py b/tests/e2e/test_prompt.py new file mode 100644 index 000000000..6e113cb41 --- /dev/null +++ b/tests/e2e/test_prompt.py @@ -0,0 +1,697 @@ +import pytest + +from langfuse._client.client import Langfuse +from tests.support.utils import create_uuid, get_api + + +def test_create_prompt(): + langfuse = Langfuse() + prompt_name = create_uuid() + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + commit_message="initial commit", + ) + + second_prompt_client = langfuse.get_prompt(prompt_name) + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.config == second_prompt_client.config + assert prompt_client.commit_message == second_prompt_client.commit_message + assert prompt_client.config == {} + + +def test_create_prompt_with_special_chars_in_name(): + langfuse = Langfuse() + prompt_name = create_uuid() + "special chars !@#$%^&*() +" + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + tags=["test"], + ) + + second_prompt_client = langfuse.get_prompt(prompt_name) + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.tags == second_prompt_client.tags + assert prompt_client.config == second_prompt_client.config + assert prompt_client.config == {} + + +def test_create_prompt_with_placeholders(): + """Test creating a prompt with placeholder messages.""" + langfuse = Langfuse() + prompt_name = create_uuid() + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt=[ + {"role": "system", "content": "System message"}, + {"type": "placeholder", "name": "context"}, + {"role": "user", "content": "User message"}, + ], + type="chat", + ) + + # Verify the full prompt structure with placeholders + assert len(prompt_client.prompt) == 3 + + # First message - system + assert prompt_client.prompt[0]["type"] == "message" + assert prompt_client.prompt[0]["role"] == "system" + assert prompt_client.prompt[0]["content"] == "System message" + # Placeholder + assert prompt_client.prompt[1]["type"] == "placeholder" + assert prompt_client.prompt[1]["name"] == "context" + # Third message - user + assert prompt_client.prompt[2]["type"] == "message" + assert prompt_client.prompt[2]["role"] == "user" + assert prompt_client.prompt[2]["content"] == "User message" + + +def test_get_prompt_with_placeholders(): + """Test retrieving a prompt with placeholders.""" + langfuse = Langfuse() + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt=[ + {"role": "system", "content": "You are {{name}}"}, + {"type": "placeholder", "name": "history"}, + {"role": "user", "content": "{{question}}"}, + ], + type="chat", + ) + + prompt_client = langfuse.get_prompt(prompt_name, type="chat", version=1) + + # Verify placeholder structure is preserved + assert len(prompt_client.prompt) == 3 + + # First message - system with variable + assert prompt_client.prompt[0]["type"] == "message" + assert prompt_client.prompt[0]["role"] == "system" + assert prompt_client.prompt[0]["content"] == "You are {{name}}" + # Placeholder + assert prompt_client.prompt[1]["type"] == "placeholder" + assert prompt_client.prompt[1]["name"] == "history" + # Third message - user with variable + assert prompt_client.prompt[2]["type"] == "message" + assert prompt_client.prompt[2]["role"] == "user" + assert prompt_client.prompt[2]["content"] == "{{question}}" + + +def test_warning_on_unresolved_placeholders(): + """Test that a warning is emitted when compiling with unresolved placeholders.""" + from unittest.mock import patch + + langfuse = Langfuse() + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt=[ + {"role": "system", "content": "You are {{name}}"}, + {"type": "placeholder", "name": "history"}, + {"role": "user", "content": "{{question}}"}, + ], + type="chat", + ) + + prompt_client = langfuse.get_prompt(prompt_name, type="chat", version=1) + + # Test that warning is emitted when compiling with unresolved placeholders + with patch("langfuse.logger.langfuse_logger.warning") as mock_warning: + # Compile without providing the 'history' placeholder + result = prompt_client.compile(name="Assistant", question="What is 2+2?") + + # Verify the warning was called with the expected message + mock_warning.assert_called_once() + warning_message = mock_warning.call_args[0][0] + assert "Placeholders ['history'] have not been resolved" in warning_message + + # Verify the result only contains the resolved messages + assert len(result) == 3 + assert result[0]["content"] == "You are Assistant" + assert result[1]["name"] == "history" + assert result[2]["content"] == "What is 2+2?" + + +def test_compiling_chat_prompt(): + langfuse = Langfuse() + prompt_name = create_uuid() + + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt=[ + { + "role": "system", + "content": "test prompt 1 with {{state}} {{target}} {{state}}", + }, + {"role": "user", "content": "test prompt 2 with {{state}}"}, + ], + labels=["production"], + type="chat", + ) + + second_prompt_client = langfuse.get_prompt(prompt_name, type="chat") + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + assert second_prompt_client.compile(target="world", state="great") == [ + {"role": "system", "content": "test prompt 1 with great world great"}, + {"role": "user", "content": "test prompt 2 with great"}, + ] + + +def test_compiling_prompt(): + langfuse = Langfuse() + prompt_name = "test_compiling_prompt" + + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt='Hello, {{target}}! I hope you are {{state}}. {{undefined_variable}}. And here is some JSON that should not be compiled: {{ "key": "value" }} \ + Here is a custom var for users using str.format instead of the mustache-style double curly braces: {custom_var}', + labels=["production"], + ) + + second_prompt_client = langfuse.get_prompt(prompt_name) + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + compiled = second_prompt_client.compile(target="world", state="great") + + assert ( + compiled + == 'Hello, world! I hope you are great. {{undefined_variable}}. And here is some JSON that should not be compiled: {{ "key": "value" }} \ + Here is a custom var for users using str.format instead of the mustache-style double curly braces: {custom_var}' + ) + + +def test_compiling_prompt_without_character_escaping(): + langfuse = Langfuse() + prompt_name = "test_compiling_prompt_without_character_escaping" + + prompt_client = langfuse.create_prompt( + name=prompt_name, prompt="Hello, {{ some_json }}", labels=["production"] + ) + + second_prompt_client = langfuse.get_prompt(prompt_name) + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + some_json = '{"key": "value"}' + compiled = second_prompt_client.compile(some_json=some_json) + + assert compiled == 'Hello, {"key": "value"}' + + +def test_compiling_prompt_with_content_as_variable_name(): + langfuse = Langfuse() + prompt_name = "test_compiling_prompt_with_content_as_variable_name" + + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt="Hello, {{ content }}!", + labels=["production"], + ) + + second_prompt_client = langfuse.get_prompt(prompt_name) + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + compiled = second_prompt_client.compile(content="Jane") + + assert compiled == "Hello, Jane!" + + +def test_create_prompt_with_null_config(): + langfuse = Langfuse(debug=False) + + langfuse.create_prompt( + name="test_null_config", + prompt="Hello, world! I hope you are great", + labels=["production"], + config=None, + ) + + prompt = langfuse.get_prompt("test_null_config") + + assert prompt.config == {} + + +def test_create_prompt_with_tags(): + langfuse = Langfuse(debug=False) + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=["tag1", "tag2"], + ) + + prompt = langfuse.get_prompt(prompt_name, version=1) + + assert prompt.tags == ["tag1", "tag2"] + + +def test_create_prompt_with_empty_tags(): + langfuse = Langfuse(debug=False) + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=[], + ) + + prompt = langfuse.get_prompt(prompt_name, version=1) + + assert prompt.tags == [] + + +def test_create_prompt_with_previous_tags(): + langfuse = Langfuse(debug=False) + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + ) + + prompt = langfuse.get_prompt(prompt_name, version=1) + + assert prompt.tags == [] + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=["tag1", "tag2"], + ) + + prompt_v2 = langfuse.get_prompt(prompt_name, version=2) + + assert prompt_v2.tags == ["tag1", "tag2"] + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + ) + + prompt_v3 = langfuse.get_prompt(prompt_name, version=3) + + assert prompt_v3.tags == ["tag1", "tag2"] + + +def test_remove_prompt_tags(): + langfuse = Langfuse(debug=False) + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=["tag1", "tag2"], + ) + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=[], + ) + + prompt_v1 = langfuse.get_prompt(prompt_name, version=1) + prompt_v2 = langfuse.get_prompt(prompt_name, version=2) + + assert prompt_v1.tags == [] + assert prompt_v2.tags == [] + + +def test_update_prompt_tags(): + langfuse = Langfuse(debug=False) + prompt_name = create_uuid() + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=["tag1", "tag2"], + ) + + prompt_v1 = langfuse.get_prompt(prompt_name, version=1) + + assert prompt_v1.tags == ["tag1", "tag2"] + + langfuse.create_prompt( + name=prompt_name, + prompt="Hello, world! I hope you are great", + tags=["tag3", "tag4"], + ) + + prompt_v2 = langfuse.get_prompt(prompt_name, version=2) + + assert prompt_v2.tags == ["tag3", "tag4"] + + +def test_get_prompt_by_version_or_label(): + langfuse = Langfuse() + prompt_name = create_uuid() + + for i in range(3): + langfuse.create_prompt( + name=prompt_name, + prompt="test prompt " + str(i + 1), + labels=["production"] if i == 1 else [], + ) + + default_prompt_client = langfuse.get_prompt(prompt_name) + assert default_prompt_client.version == 2 + assert default_prompt_client.prompt == "test prompt 2" + assert default_prompt_client.labels == ["production"] + + first_prompt_client = langfuse.get_prompt(prompt_name, version=1) + assert first_prompt_client.version == 1 + assert first_prompt_client.prompt == "test prompt 1" + assert first_prompt_client.labels == [] + + second_prompt_client = langfuse.get_prompt(prompt_name, version=2) + assert second_prompt_client.version == 2 + assert second_prompt_client.prompt == "test prompt 2" + assert second_prompt_client.labels == ["production"] + + third_prompt_client = langfuse.get_prompt(prompt_name, label="latest") + assert third_prompt_client.version == 3 + assert third_prompt_client.prompt == "test prompt 3" + assert third_prompt_client.labels == ["latest"] + + +def test_prompt_end_to_end(): + langfuse = Langfuse(debug=False) + + langfuse.create_prompt( + name="test", + prompt="Hello, {{target}}! I hope you are {{state}}.", + labels=["production"], + config={"temperature": 0.5}, + ) + + prompt = langfuse.get_prompt("test") + + prompt_str = prompt.compile(target="world", state="great") + assert prompt_str == "Hello, world! I hope you are great." + assert prompt.config == {"temperature": 0.5} + + generation = langfuse.start_observation( + as_type="generation", + name="mygen", + input=prompt_str, + prompt=prompt, + ).end() + + # to check that these do not error + generation.update(prompt=prompt) + + langfuse.flush() + + api = get_api() + + trace = api.trace.get(generation.trace_id) + + assert len(trace.observations) == 1 + + generation = trace.observations[0] + assert generation.prompt_id is not None + + observation = api.legacy.observations_v1.get(generation.id) + + assert observation.prompt_id is not None + + +def test_do_not_return_fallback_if_fetch_success(): + langfuse = Langfuse() + prompt_name = create_uuid() + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + ) + + second_prompt_client = langfuse.get_prompt(prompt_name, fallback="fallback") + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.config == second_prompt_client.config + assert prompt_client.config == {} + + +def test_fallback_text_prompt(): + langfuse = Langfuse() + + fallback_text_prompt = "this is a fallback text prompt with {{variable}}" + + # Should throw an error if prompt not found and no fallback provided + with pytest.raises(Exception): + langfuse.get_prompt("nonexistent_prompt") + + prompt = langfuse.get_prompt("nonexistent_prompt", fallback=fallback_text_prompt) + + assert prompt.prompt == fallback_text_prompt + assert ( + prompt.compile(variable="value") == "this is a fallback text prompt with value" + ) + + +def test_fallback_chat_prompt(): + langfuse = Langfuse() + fallback_chat_prompt = [ + {"role": "system", "content": "fallback system"}, + {"role": "user", "content": "fallback user name {{name}}"}, + ] + + # Should throw an error if prompt not found and no fallback provided + with pytest.raises(Exception): + langfuse.get_prompt("nonexistent_chat_prompt", type="chat") + + prompt = langfuse.get_prompt( + "nonexistent_chat_prompt", type="chat", fallback=fallback_chat_prompt + ) + + # Check that the prompt structure contains the fallback data (allowing for internal formatting) + assert len(prompt.prompt) == len(fallback_chat_prompt) + assert all(msg["type"] == "message" for msg in prompt.prompt) + assert prompt.prompt[0]["role"] == "system" + assert prompt.prompt[0]["content"] == "fallback system" + assert prompt.prompt[1]["role"] == "user" + assert prompt.prompt[1]["content"] == "fallback user name {{name}}" + assert prompt.compile(name="Jane") == [ + {"role": "system", "content": "fallback system"}, + {"role": "user", "content": "fallback user name Jane"}, + ] + + +def test_do_not_link_observation_if_fallback(): + langfuse = Langfuse() + + fallback_text_prompt = "this is a fallback text prompt with {{variable}}" + + # Should throw an error if prompt not found and no fallback provided + with pytest.raises(Exception): + langfuse.get_prompt("nonexistent_prompt") + + prompt = langfuse.get_prompt("nonexistent_prompt", fallback=fallback_text_prompt) + + generation = langfuse.start_observation( + as_type="generation", + name="mygen", + prompt=prompt, + input="this is a test input", + ).end() + langfuse.flush() + + api = get_api() + trace = api.trace.get(generation.trace_id) + + assert len(trace.observations) == 1 + assert trace.observations[0].prompt_id is None + + +def test_variable_names_on_content_with_variable_names(): + langfuse = Langfuse() + + prompt_client = langfuse.create_prompt( + name="test_variable_names_1", + prompt="test prompt with var names {{ var1 }} {{ var2 }}", + labels=["production"], + type="text", + ) + + second_prompt_client = langfuse.get_prompt("test_variable_names_1") + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + var_names = second_prompt_client.variables + + assert var_names == ["var1", "var2"] + + +def test_variable_names_on_content_with_no_variable_names(): + langfuse = Langfuse() + + prompt_client = langfuse.create_prompt( + name="test_variable_names_2", + prompt="test prompt with no var names", + labels=["production"], + type="text", + ) + + second_prompt_client = langfuse.get_prompt("test_variable_names_2") + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + var_names = second_prompt_client.variables + + assert var_names == [] + + +def test_variable_names_on_content_with_variable_names_chat_messages(): + langfuse = Langfuse() + + prompt_client = langfuse.create_prompt( + name="test_variable_names_3", + prompt=[ + { + "role": "system", + "content": "test prompt with template vars {{ var1 }} {{ var2 }}", + }, + {"role": "user", "content": "test prompt 2 with template vars {{ var3 }}"}, + ], + labels=["production"], + type="chat", + ) + + second_prompt_client = langfuse.get_prompt("test_variable_names_3") + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + var_names = second_prompt_client.variables + + assert var_names == ["var1", "var2", "var3"] + + +def test_variable_names_on_content_with_no_variable_names_chat_messages(): + langfuse = Langfuse() + prompt_name = "test_variable_names_on_content_with_no_variable_names_chat_messages" + + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt=[ + {"role": "system", "content": "test prompt with no template vars"}, + {"role": "user", "content": "test prompt 2 with no template vars"}, + ], + labels=["production"], + type="chat", + ) + + second_prompt_client = langfuse.get_prompt(prompt_name) + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.labels == ["production", "latest"] + + var_names = second_prompt_client.variables + + assert var_names == [] + + +def test_update_prompt(): + langfuse = Langfuse() + prompt_name = create_uuid() + + # Create initial prompt + langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + ) + + # Update prompt labels + updated_prompt = langfuse.update_prompt( + name=prompt_name, + version=1, + new_labels=["john", "doe"], + ) + + # Fetch prompt after update (should be invalidated) + fetched_prompt = langfuse.get_prompt(prompt_name) + + # Verify the fetched prompt matches the updated values + assert fetched_prompt.name == prompt_name + assert fetched_prompt.version == 1 + print(f"Fetched prompt labels: {fetched_prompt.labels}") + print(f"Updated prompt labels: {updated_prompt.labels}") + + # production was set by the first call, latest is managed and set by Langfuse + expected_labels = sorted(["latest", "doe", "production", "john"]) + assert sorted(fetched_prompt.labels) == expected_labels + assert sorted(updated_prompt.labels) == expected_labels + + +def test_update_prompt_in_folder(): + langfuse = Langfuse() + prompt_name = f"some-folder/{create_uuid()}" + + # Create initial prompt + langfuse.create_prompt( + name=prompt_name, + prompt="test prompt", + labels=["production"], + ) + + old_prompt_obj = langfuse.get_prompt(prompt_name) + + updated_prompt = langfuse.update_prompt( + name=old_prompt_obj.name, + version=old_prompt_obj.version, + new_labels=["john", "doe"], + ) + + # Fetch prompt after update (should be invalidated) + fetched_prompt = langfuse.get_prompt(prompt_name) + + # Verify the fetched prompt matches the updated values + assert fetched_prompt.name == prompt_name + assert fetched_prompt.version == 1 + print(f"Fetched prompt labels: {fetched_prompt.labels}") + print(f"Updated prompt labels: {updated_prompt.labels}") + + # production was set by the first call, latest is managed and set by Langfuse + expected_labels = sorted(["latest", "doe", "production", "john"]) + assert sorted(fetched_prompt.labels) == expected_labels + assert sorted(updated_prompt.labels) == expected_labels diff --git a/tests/live_provider/__init__.py b/tests/live_provider/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/live_provider/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/test_langchain.py b/tests/live_provider/test_langchain.py similarity index 94% rename from tests/test_langchain.py rename to tests/live_provider/test_langchain.py index fa2bcfddb..c1b13222e 100644 --- a/tests/test_langchain.py +++ b/tests/live_provider/test_langchain.py @@ -18,7 +18,7 @@ from langfuse._client.client import Langfuse from langfuse.langchain import CallbackHandler -from tests.utils import create_uuid, encode_file_to_base64, get_api +from tests.support.utils import create_uuid, encode_file_to_base64, get_api def test_callback_generated_from_trace_chat(): @@ -51,14 +51,16 @@ def test_callback_generated_from_trace_chat(): assert trace.id == trace_id - assert len(trace.observations) == 3 + assert len(trace.observations) >= 2 + assert any(observation.name == "parent" for observation in trace.observations) - langchain_generation_span = list( - filter( - lambda o: o.type == "GENERATION" and o.name == "ChatOpenAI", - trace.observations, - ) - )[0] + generation_observations = [ + observation + for observation in trace.observations + if observation.type == "GENERATION" and observation.name == "ChatOpenAI" + ] + assert len(generation_observations) == 1 + langchain_generation_span = generation_observations[0] assert langchain_generation_span.usage_details["input"] > 0 assert langchain_generation_span.usage_details["output"] > 0 @@ -98,12 +100,13 @@ def test_callback_generated_from_lcel_chain(): assert len(trace.observations) > 0 - langchain_generation_span = list( - filter( - lambda o: o.type == "GENERATION" and o.name == "ChatOpenAI", - trace.observations, - ) - )[0] + generation_observations = [ + observation + for observation in trace.observations + if observation.type == "GENERATION" + ] + assert len(generation_observations) > 0 + langchain_generation_span = generation_observations[0] langchain_root_spans = [ observation for observation in trace.observations @@ -294,19 +297,26 @@ def test_openai_instruct_usage(): observations = get_api().trace.get(trace_id).observations - # Add 1 to account for the wrapping span - assert len(observations) == 4 + assert len(observations) >= 3 + assert any( + observation.name == "openai_instruct_usage_test" and observation.type == "SPAN" + for observation in observations + ) - for observation in observations: - if observation.type == "GENERATION": - assert observation.output is not None - assert observation.output != "" - assert observation.input is not None - assert observation.input != "" - assert observation.usage is not None - assert observation.usage_details["input"] is not None - assert observation.usage_details["output"] is not None - assert observation.usage_details["total"] is not None + generation_observations = [ + observation for observation in observations if observation.type == "GENERATION" + ] + assert len(generation_observations) == len(input_list) + + for observation in generation_observations: + assert observation.output is not None + assert observation.output != "" + assert observation.input is not None + assert observation.input != "" + assert observation.usage is not None + assert observation.usage_details["input"] is not None + assert observation.usage_details["output"] is not None + assert observation.usage_details["total"] is not None def test_get_langchain_prompt_with_jinja2(): @@ -869,7 +879,10 @@ def test_multimodal(): trace = get_api().trace.get(trace_id=trace_id) - assert len(trace.observations) == 3 + assert len(trace.observations) >= 2 + assert any( + observation.name == "test_multimodal" for observation in trace.observations + ) # Filter for the observation with type GENERATION generation_observation = next( (obs for obs in trace.observations if obs.type == "GENERATION"), None diff --git a/tests/test_langchain_integration.py b/tests/live_provider/test_langchain_integration.py similarity index 99% rename from tests/test_langchain_integration.py rename to tests/live_provider/test_langchain_integration.py index c7e4a9418..edb5455c4 100644 --- a/tests/test_langchain_integration.py +++ b/tests/live_provider/test_langchain_integration.py @@ -7,9 +7,7 @@ from langfuse import Langfuse from langfuse.langchain import CallbackHandler -from tests.utils import get_api - -from .utils import create_uuid +from tests.support.utils import create_uuid, get_api def _is_streaming_response(response): diff --git a/tests/test_openai.py b/tests/live_provider/test_openai.py similarity index 99% rename from tests/test_openai.py rename to tests/live_provider/test_openai.py index 47f17a5c8..3cc05c9c6 100644 --- a/tests/test_openai.py +++ b/tests/live_provider/test_openai.py @@ -6,9 +6,9 @@ from pydantic import BaseModel from langfuse._client.client import Langfuse -from tests.utils import create_uuid, encode_file_to_base64, get_api +from tests.support.utils import create_uuid, encode_file_to_base64, get_api -langfuse = Langfuse() +langfuse: Langfuse | None = None @pytest.fixture(scope="module") @@ -22,6 +22,20 @@ def openai(): importlib.reload(openai) +@pytest.fixture(scope="module") +def langfuse_client(): + client = Langfuse() + yield client + client.shutdown() + + +@pytest.fixture(autouse=True) +def _bind_langfuse_client(langfuse_client): + global langfuse + langfuse = langfuse_client + yield + + def test_openai_chat_completion(openai): generation_name = create_uuid() completion = openai.OpenAI().chat.completions.create( diff --git a/tests/live_provider/test_prompt.py b/tests/live_provider/test_prompt.py new file mode 100644 index 000000000..a64f26f45 --- /dev/null +++ b/tests/live_provider/test_prompt.py @@ -0,0 +1,87 @@ +import openai + +from langfuse._client.client import Langfuse +from tests.support.utils import create_uuid + + +def test_create_chat_prompt(): + langfuse = Langfuse() + prompt_name = create_uuid() + + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt=[ + {"role": "system", "content": "test prompt 1 with {{animal}}"}, + {"role": "user", "content": "test prompt 2 with {{occupation}}"}, + ], + labels=["production"], + tags=["test"], + type="chat", + commit_message="initial commit", + ) + + second_prompt_client = langfuse.get_prompt(prompt_name, type="chat") + + completion = openai.OpenAI().chat.completions.create( + model="gpt-4", + messages=prompt_client.compile(animal="dog", occupation="doctor"), + ) + + assert len(completion.choices) > 0 + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.prompt == second_prompt_client.prompt + assert prompt_client.config == second_prompt_client.config + assert prompt_client.labels == ["production", "latest"] + assert prompt_client.tags == second_prompt_client.tags + assert prompt_client.commit_message == second_prompt_client.commit_message + assert prompt_client.config == {} + + +def test_create_chat_prompt_with_placeholders(): + langfuse = Langfuse() + prompt_name = create_uuid() + + prompt_client = langfuse.create_prompt( + name=prompt_name, + prompt=[ + {"role": "system", "content": "You are a {{role}} assistant"}, + {"type": "placeholder", "name": "history"}, + {"role": "user", "content": "Help me with {{task}}"}, + ], + labels=["production"], + tags=["test"], + type="chat", + commit_message="initial commit", + ) + + second_prompt_client = langfuse.get_prompt(prompt_name, type="chat") + messages = second_prompt_client.compile( + role="helpful", + task="coding", + history=[ + {"role": "user", "content": "Example: {{task}}"}, + {"role": "assistant", "content": "Example response"}, + ], + ) + + completion = openai.OpenAI().chat.completions.create( + model="gpt-4", + messages=messages, + ) + + assert len(completion.choices) > 0 + assert len(messages) == 4 + assert messages[0]["content"] == "You are a helpful assistant" + assert messages[1]["content"] == "Example: coding" + assert messages[2]["content"] == "Example response" + assert messages[3]["content"] == "Help me with coding" + + assert prompt_client.name == second_prompt_client.name + assert prompt_client.version == second_prompt_client.version + assert prompt_client.config == second_prompt_client.config + assert prompt_client.labels == ["production", "latest"] + assert prompt_client.tags == second_prompt_client.tags + assert prompt_client.commit_message == second_prompt_client.commit_message + assert prompt_client.config == {} diff --git a/tests/support/__init__.py b/tests/support/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/support/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/support/api_wrapper.py b/tests/support/api_wrapper.py new file mode 100644 index 000000000..c4519252f --- /dev/null +++ b/tests/support/api_wrapper.py @@ -0,0 +1,130 @@ +import os + +import httpx + +from langfuse.api.commons.errors.not_found_error import NotFoundError +from tests.support.retry import ( + DEFAULT_RETRY_INTERVAL_SECONDS, + DEFAULT_RETRY_TIMEOUT_SECONDS, + is_not_found_payload, + retry_until_ready, +) + + +class LangfuseAPI: + def __init__(self, username=None, password=None, base_url=None): + username = username if username else os.environ["LANGFUSE_PUBLIC_KEY"] + password = password if password else os.environ["LANGFUSE_SECRET_KEY"] + self.auth = (username, password) + self.BASE_URL = base_url if base_url else os.environ["LANGFUSE_BASE_URL"] + + def _get_json( + self, + url, + params=None, + *, + retry=True, + is_result_ready=None, + timeout_seconds=DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds=DEFAULT_RETRY_INTERVAL_SECONDS, + ): + def _request(): + response = httpx.get(url, params=params, auth=self.auth) + payload = response.json() + + if response.status_code == 404 and is_not_found_payload(payload): + raise NotFoundError(body=payload, headers=dict(response.headers)) + + return payload + + if not retry: + return _request() + + return retry_until_ready( + _request, + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) + + def get_observation( + self, + observation_id, + *, + retry=True, + is_result_ready=None, + timeout_seconds=DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds=DEFAULT_RETRY_INTERVAL_SECONDS, + ): + url = f"{self.BASE_URL}/api/public/observations/{observation_id}" + return self._get_json( + url, + retry=retry, + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) + + def get_scores( + self, + page=None, + limit=None, + user_id=None, + name=None, + *, + retry=True, + is_result_ready=None, + timeout_seconds=DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds=DEFAULT_RETRY_INTERVAL_SECONDS, + ): + params = {"page": page, "limit": limit, "userId": user_id, "name": name} + url = f"{self.BASE_URL}/api/public/scores" + return self._get_json( + url, + params=params, + retry=retry, + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) + + def get_traces( + self, + page=None, + limit=None, + user_id=None, + name=None, + *, + retry=True, + is_result_ready=None, + timeout_seconds=DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds=DEFAULT_RETRY_INTERVAL_SECONDS, + ): + params = {"page": page, "limit": limit, "userId": user_id, "name": name} + url = f"{self.BASE_URL}/api/public/traces" + return self._get_json( + url, + params=params, + retry=retry, + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) + + def get_trace( + self, + trace_id, + *, + retry=True, + is_result_ready=None, + timeout_seconds=DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds=DEFAULT_RETRY_INTERVAL_SECONDS, + ): + url = f"{self.BASE_URL}/api/public/traces/{trace_id}" + return self._get_json( + url, + retry=retry, + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) diff --git a/tests/support/retry.py b/tests/support/retry.py new file mode 100644 index 000000000..8a44089d9 --- /dev/null +++ b/tests/support/retry.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import os +from time import monotonic, sleep +from typing import Callable, TypeVar + +from langfuse.api.commons.errors.not_found_error import NotFoundError +from langfuse.api.core.api_error import ApiError + +T = TypeVar("T") + +DEFAULT_RETRY_TIMEOUT_SECONDS = float( + os.environ.get("LANGFUSE_E2E_READ_TIMEOUT_SECONDS", "12") +) +DEFAULT_RETRY_INTERVAL_SECONDS = float( + os.environ.get("LANGFUSE_E2E_READ_INTERVAL_SECONDS", "0.25") +) + + +def is_eventual_consistency_error(error: Exception) -> bool: + if isinstance(error, NotFoundError): + return True + + if not isinstance(error, ApiError): + return False + + body = error.body + return isinstance(body, dict) and body.get("error") == "LangfuseNotFoundError" + + +def is_not_found_payload(payload: object) -> bool: + return isinstance(payload, dict) and payload.get("error") == "LangfuseNotFoundError" + + +def retry_until_ready( + operation: Callable[[], T], + *, + is_retryable_error: Callable[[Exception], bool] = is_eventual_consistency_error, + is_result_ready: Callable[[T], bool] | None = None, + timeout_seconds: float = DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds: float = DEFAULT_RETRY_INTERVAL_SECONDS, +) -> T: + deadline = monotonic() + timeout_seconds + last_error: Exception | None = None + + while True: + try: + result = operation() + except Exception as error: + if not is_retryable_error(error) or monotonic() >= deadline: + raise + + last_error = error + else: + last_error = None + if is_result_ready is None or is_result_ready(result): + return result + + if monotonic() >= deadline: + return result + + sleep(interval_seconds) + + if monotonic() >= deadline and last_error is not None: + raise last_error diff --git a/tests/support/utils.py b/tests/support/utils.py new file mode 100644 index 000000000..a29274d3a --- /dev/null +++ b/tests/support/utils.py @@ -0,0 +1,110 @@ +import base64 +import os +from typing import Any, Callable, TypeVar +from uuid import uuid4 + +from langfuse.api import LangfuseAPI +from tests.support.retry import ( + DEFAULT_RETRY_INTERVAL_SECONDS, + DEFAULT_RETRY_TIMEOUT_SECONDS, + retry_until_ready, +) + +READ_METHOD_NAMES = {"get", "get_by_id", "get_many", "get_run", "list"} +PAGINATION_ARGUMENTS = {"limit", "page"} +T = TypeVar("T") + + +def _has_filters(kwargs: dict[str, Any]) -> bool: + return any( + key not in PAGINATION_ARGUMENTS and value is not None + for key, value in kwargs.items() + ) + + +class _RetryingApiProxy: + def __init__(self, target: Any): + self._target = target + + def __getattr__(self, name: str) -> Any: + attr = getattr(self._target, name) + + if callable(attr): + if name not in READ_METHOD_NAMES: + return attr + + def _call(*args: Any, **kwargs: Any) -> Any: + return retry_until_ready( + lambda: attr(*args, **kwargs), + is_result_ready=_result_ready(name, kwargs), + ) + + return _call + + if isinstance(attr, (str, bytes, int, float, bool, list, dict, tuple, set)): + return attr + + if attr is None: + return None + + return _RetryingApiProxy(attr) + + +def _result_ready(method_name: str, kwargs: dict[str, Any]): + if method_name not in {"get_many", "list"} or not _has_filters(kwargs): + return None + + def _has_data(result: Any) -> bool: + data = getattr(result, "data", None) + return data is None or len(data) > 0 + + return _has_data + + +def create_uuid(): + return str(uuid4()) + + +def get_api(*, retry: bool = True): + client = LangfuseAPI( + username=os.environ.get("LANGFUSE_PUBLIC_KEY"), + password=os.environ.get("LANGFUSE_SECRET_KEY"), + base_url=os.environ.get("LANGFUSE_BASE_URL"), + ) + return _RetryingApiProxy(client) if retry else client + + +def wait_for_result( + operation: Callable[[], T], + *, + is_result_ready: Callable[[T], bool] | None = None, + timeout_seconds: float = DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds: float = DEFAULT_RETRY_INTERVAL_SECONDS, +) -> T: + return retry_until_ready( + operation, + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) + + +def wait_for_trace( + trace_id: str, + *, + is_result_ready: Callable[[Any], bool] | None = None, + timeout_seconds: float = DEFAULT_RETRY_TIMEOUT_SECONDS, + interval_seconds: float = DEFAULT_RETRY_INTERVAL_SECONDS, +): + api = get_api(retry=False) + return wait_for_result( + lambda: api.trace.get(trace_id), + is_result_ready=is_result_ready, + timeout_seconds=timeout_seconds, + interval_seconds=interval_seconds, + ) + + +def encode_file_to_base64(image_path) -> str: + with open(image_path, "rb") as file: + return base64.b64encode(file.read()).decode("utf-8") diff --git a/tests/test_prompt.py b/tests/test_prompt.py deleted file mode 100644 index 3c4c5c013..000000000 --- a/tests/test_prompt.py +++ /dev/null @@ -1,1524 +0,0 @@ -from time import sleep -from unittest.mock import Mock, patch - -import openai -import pytest - -from langfuse._client.client import Langfuse -from langfuse._utils.prompt_cache import ( - DEFAULT_PROMPT_CACHE_TTL_SECONDS, - PromptCache, - PromptCacheItem, -) -from langfuse.api import NotFoundError, Prompt_Chat, Prompt_Text -from langfuse.model import ChatPromptClient, TextPromptClient -from tests.utils import create_uuid, get_api - - -def test_create_prompt(): - langfuse = Langfuse() - prompt_name = create_uuid() - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt="test prompt", - labels=["production"], - commit_message="initial commit", - ) - - second_prompt_client = langfuse.get_prompt(prompt_name) - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.config == second_prompt_client.config - assert prompt_client.commit_message == second_prompt_client.commit_message - assert prompt_client.config == {} - - -def test_create_prompt_with_special_chars_in_name(): - langfuse = Langfuse() - prompt_name = create_uuid() + "special chars !@#$%^&*() +" - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt="test prompt", - labels=["production"], - tags=["test"], - ) - - second_prompt_client = langfuse.get_prompt(prompt_name) - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.tags == second_prompt_client.tags - assert prompt_client.config == second_prompt_client.config - assert prompt_client.config == {} - - -def test_create_chat_prompt(): - langfuse = Langfuse() - prompt_name = create_uuid() - - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt=[ - {"role": "system", "content": "test prompt 1 with {{animal}}"}, - {"role": "user", "content": "test prompt 2 with {{occupation}}"}, - ], - labels=["production"], - tags=["test"], - type="chat", - commit_message="initial commit", - ) - - second_prompt_client = langfuse.get_prompt(prompt_name, type="chat") - - # Create a test generation - completion = openai.OpenAI().chat.completions.create( - model="gpt-4", - messages=prompt_client.compile(animal="dog", occupation="doctor"), - ) - - assert len(completion.choices) > 0 - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.config == second_prompt_client.config - assert prompt_client.labels == ["production", "latest"] - assert prompt_client.tags == second_prompt_client.tags - assert prompt_client.commit_message == second_prompt_client.commit_message - assert prompt_client.config == {} - - -def test_create_chat_prompt_with_placeholders(): - langfuse = Langfuse() - prompt_name = create_uuid() - - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt=[ - {"role": "system", "content": "You are a {{role}} assistant"}, - {"type": "placeholder", "name": "history"}, - {"role": "user", "content": "Help me with {{task}}"}, - ], - labels=["production"], - tags=["test"], - type="chat", - commit_message="initial commit", - ) - - second_prompt_client = langfuse.get_prompt(prompt_name, type="chat") - messages = second_prompt_client.compile( - role="helpful", - task="coding", - history=[ - {"role": "user", "content": "Example: {{task}}"}, - {"role": "assistant", "content": "Example response"}, - ], - ) - - # Create a test generation using compiled messages - completion = openai.OpenAI().chat.completions.create( - model="gpt-4", - messages=messages, - ) - - assert len(completion.choices) > 0 - assert len(messages) == 4 - assert messages[0]["content"] == "You are a helpful assistant" - assert messages[1]["content"] == "Example: coding" - assert messages[2]["content"] == "Example response" - assert messages[3]["content"] == "Help me with coding" - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.config == second_prompt_client.config - assert prompt_client.labels == ["production", "latest"] - assert prompt_client.tags == second_prompt_client.tags - assert prompt_client.commit_message == second_prompt_client.commit_message - assert prompt_client.config == {} - - -def test_create_prompt_with_placeholders(): - """Test creating a prompt with placeholder messages.""" - langfuse = Langfuse() - prompt_name = create_uuid() - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt=[ - {"role": "system", "content": "System message"}, - {"type": "placeholder", "name": "context"}, - {"role": "user", "content": "User message"}, - ], - type="chat", - ) - - # Verify the full prompt structure with placeholders - assert len(prompt_client.prompt) == 3 - - # First message - system - assert prompt_client.prompt[0]["type"] == "message" - assert prompt_client.prompt[0]["role"] == "system" - assert prompt_client.prompt[0]["content"] == "System message" - # Placeholder - assert prompt_client.prompt[1]["type"] == "placeholder" - assert prompt_client.prompt[1]["name"] == "context" - # Third message - user - assert prompt_client.prompt[2]["type"] == "message" - assert prompt_client.prompt[2]["role"] == "user" - assert prompt_client.prompt[2]["content"] == "User message" - - -def test_get_prompt_with_placeholders(): - """Test retrieving a prompt with placeholders.""" - langfuse = Langfuse() - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt=[ - {"role": "system", "content": "You are {{name}}"}, - {"type": "placeholder", "name": "history"}, - {"role": "user", "content": "{{question}}"}, - ], - type="chat", - ) - - prompt_client = langfuse.get_prompt(prompt_name, type="chat", version=1) - - # Verify placeholder structure is preserved - assert len(prompt_client.prompt) == 3 - - # First message - system with variable - assert prompt_client.prompt[0]["type"] == "message" - assert prompt_client.prompt[0]["role"] == "system" - assert prompt_client.prompt[0]["content"] == "You are {{name}}" - # Placeholder - assert prompt_client.prompt[1]["type"] == "placeholder" - assert prompt_client.prompt[1]["name"] == "history" - # Third message - user with variable - assert prompt_client.prompt[2]["type"] == "message" - assert prompt_client.prompt[2]["role"] == "user" - assert prompt_client.prompt[2]["content"] == "{{question}}" - - -@pytest.mark.parametrize( - ("variables", "placeholders", "expected_len", "expected_contents"), - [ - # 0. Variables only, no placeholders. Unresolved placeholders kept in output - ( - {"role": "helpful", "task": "coding"}, - {}, - 3, - [ - "You are a helpful assistant", - None, - "Help me with coding", - ], # None = placeholder - ), - # 1. No variables, no placeholders. Expect verbatim message+placeholder output - ( - {}, - {}, - 3, - ["You are a {{role}} assistant", None, "Help me with {{task}}"], - ), # None = placeholder - # 2. Placeholders only, empty variables. Expect output with placeholders filled in - ( - {}, - { - "examples": [ - {"role": "user", "content": "Example question"}, - {"role": "assistant", "content": "Example answer"}, - ], - }, - 4, - [ - "You are a {{role}} assistant", - "Example question", - "Example answer", - "Help me with {{task}}", - ], - ), - # 3. Both variables and placeholders. Expect fully compiled output - ( - {"role": "helpful", "task": "coding"}, - { - "examples": [ - {"role": "user", "content": "Show me {{task}}"}, - {"role": "assistant", "content": "Here's {{task}}"}, - ], - }, - 4, - [ - "You are a helpful assistant", - "Show me coding", - "Here's coding", - "Help me with coding", - ], - ), - # # Empty placeholder array - # This is expected to fail! If the user provides a placeholder, it should contain an array - # ( - # {"role": "helpful", "task": "coding"}, - # {"examples": []}, - # 2, - # ["You are a helpful assistant", "Help me with coding"], - # ), - # 4. Unused placeholder fill ins. Unresolved placeholders kept in output - ( - {"role": "helpful", "task": "coding"}, - {"unused": [{"role": "user", "content": "Won't appear"}]}, - 3, - [ - "You are a helpful assistant", - None, - "Help me with coding", - ], # None = placeholder - ), - # 5. Placeholder with non-list value (should log warning and append as string) - ( - {"role": "helpful", "task": "coding"}, - {"examples": "not a list"}, - 3, - [ - "You are a helpful assistant", - "not a list", # String value appended directly - "Help me with coding", - ], - ), - # 6. Placeholder with invalid message structure (should log warning and include both) - ( - {"role": "helpful", "task": "coding"}, - { - "examples": [ - "invalid message", - {"role": "user", "content": "valid message"}, - ] - }, - 4, - [ - "You are a helpful assistant", - "['invalid message', {'role': 'user', 'content': 'valid message'}]", # Invalid structure becomes string - "valid message", # Valid message processed normally - "Help me with coding", - ], - ), - ], -) -def test_compile_with_placeholders( - variables, placeholders, expected_len, expected_contents -) -> None: - """Test compile_with_placeholders with different variable/placeholder combinations.""" - from langfuse.api import Prompt_Chat - from langfuse.model import ChatPromptClient - - mock_prompt = Prompt_Chat( - name="test_prompt", - version=1, - type="chat", - config={}, - tags=[], - labels=[], - prompt=[ - {"role": "system", "content": "You are a {{role}} assistant"}, - {"type": "placeholder", "name": "examples"}, - {"role": "user", "content": "Help me with {{task}}"}, - ], - ) - - compile_kwargs = {**placeholders, **variables} - result = ChatPromptClient(mock_prompt).compile(**compile_kwargs) - - assert len(result) == expected_len - for i, expected_content in enumerate(expected_contents): - if expected_content is None: - # This should be an unresolved placeholder - assert "type" in result[i] and result[i]["type"] == "placeholder" - elif isinstance(result[i], str): - # This is a string value from invalid placeholder - assert result[i] == expected_content - else: - # This should be a regular message - assert "content" in result[i] - assert result[i]["content"] == expected_content - - -def test_warning_on_unresolved_placeholders(): - """Test that a warning is emitted when compiling with unresolved placeholders.""" - from unittest.mock import patch - - langfuse = Langfuse() - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt=[ - {"role": "system", "content": "You are {{name}}"}, - {"type": "placeholder", "name": "history"}, - {"role": "user", "content": "{{question}}"}, - ], - type="chat", - ) - - prompt_client = langfuse.get_prompt(prompt_name, type="chat", version=1) - - # Test that warning is emitted when compiling with unresolved placeholders - with patch("langfuse.logger.langfuse_logger.warning") as mock_warning: - # Compile without providing the 'history' placeholder - result = prompt_client.compile(name="Assistant", question="What is 2+2?") - - # Verify the warning was called with the expected message - mock_warning.assert_called_once() - warning_message = mock_warning.call_args[0][0] - assert "Placeholders ['history'] have not been resolved" in warning_message - - # Verify the result only contains the resolved messages - assert len(result) == 3 - assert result[0]["content"] == "You are Assistant" - assert result[1]["name"] == "history" - assert result[2]["content"] == "What is 2+2?" - - -def test_compiling_chat_prompt(): - langfuse = Langfuse() - prompt_name = create_uuid() - - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt=[ - { - "role": "system", - "content": "test prompt 1 with {{state}} {{target}} {{state}}", - }, - {"role": "user", "content": "test prompt 2 with {{state}}"}, - ], - labels=["production"], - type="chat", - ) - - second_prompt_client = langfuse.get_prompt(prompt_name, type="chat") - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - assert second_prompt_client.compile(target="world", state="great") == [ - {"role": "system", "content": "test prompt 1 with great world great"}, - {"role": "user", "content": "test prompt 2 with great"}, - ] - - -def test_compiling_prompt(): - langfuse = Langfuse() - prompt_name = "test_compiling_prompt" - - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt='Hello, {{target}}! I hope you are {{state}}. {{undefined_variable}}. And here is some JSON that should not be compiled: {{ "key": "value" }} \ - Here is a custom var for users using str.format instead of the mustache-style double curly braces: {custom_var}', - labels=["production"], - ) - - second_prompt_client = langfuse.get_prompt(prompt_name) - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - compiled = second_prompt_client.compile(target="world", state="great") - - assert ( - compiled - == 'Hello, world! I hope you are great. {{undefined_variable}}. And here is some JSON that should not be compiled: {{ "key": "value" }} \ - Here is a custom var for users using str.format instead of the mustache-style double curly braces: {custom_var}' - ) - - -def test_compiling_prompt_without_character_escaping(): - langfuse = Langfuse() - prompt_name = "test_compiling_prompt_without_character_escaping" - - prompt_client = langfuse.create_prompt( - name=prompt_name, prompt="Hello, {{ some_json }}", labels=["production"] - ) - - second_prompt_client = langfuse.get_prompt(prompt_name) - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - some_json = '{"key": "value"}' - compiled = second_prompt_client.compile(some_json=some_json) - - assert compiled == 'Hello, {"key": "value"}' - - -def test_compiling_prompt_with_content_as_variable_name(): - langfuse = Langfuse() - prompt_name = "test_compiling_prompt_with_content_as_variable_name" - - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt="Hello, {{ content }}!", - labels=["production"], - ) - - second_prompt_client = langfuse.get_prompt(prompt_name) - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - compiled = second_prompt_client.compile(content="Jane") - - assert compiled == "Hello, Jane!" - - -def test_create_prompt_with_null_config(): - langfuse = Langfuse(debug=False) - - langfuse.create_prompt( - name="test_null_config", - prompt="Hello, world! I hope you are great", - labels=["production"], - config=None, - ) - - prompt = langfuse.get_prompt("test_null_config") - - assert prompt.config == {} - - -def test_create_prompt_with_tags(): - langfuse = Langfuse(debug=False) - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=["tag1", "tag2"], - ) - - prompt = langfuse.get_prompt(prompt_name, version=1) - - assert prompt.tags == ["tag1", "tag2"] - - -def test_create_prompt_with_empty_tags(): - langfuse = Langfuse(debug=False) - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=[], - ) - - prompt = langfuse.get_prompt(prompt_name, version=1) - - assert prompt.tags == [] - - -def test_create_prompt_with_previous_tags(): - langfuse = Langfuse(debug=False) - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - ) - - prompt = langfuse.get_prompt(prompt_name, version=1) - - assert prompt.tags == [] - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=["tag1", "tag2"], - ) - - prompt_v2 = langfuse.get_prompt(prompt_name, version=2) - - assert prompt_v2.tags == ["tag1", "tag2"] - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - ) - - prompt_v3 = langfuse.get_prompt(prompt_name, version=3) - - assert prompt_v3.tags == ["tag1", "tag2"] - - -def test_remove_prompt_tags(): - langfuse = Langfuse(debug=False) - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=["tag1", "tag2"], - ) - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=[], - ) - - prompt_v1 = langfuse.get_prompt(prompt_name, version=1) - prompt_v2 = langfuse.get_prompt(prompt_name, version=2) - - assert prompt_v1.tags == [] - assert prompt_v2.tags == [] - - -def test_update_prompt_tags(): - langfuse = Langfuse(debug=False) - prompt_name = create_uuid() - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=["tag1", "tag2"], - ) - - prompt_v1 = langfuse.get_prompt(prompt_name, version=1) - - assert prompt_v1.tags == ["tag1", "tag2"] - - langfuse.create_prompt( - name=prompt_name, - prompt="Hello, world! I hope you are great", - tags=["tag3", "tag4"], - ) - - prompt_v2 = langfuse.get_prompt(prompt_name, version=2) - - assert prompt_v2.tags == ["tag3", "tag4"] - - -def test_get_prompt_by_version_or_label(): - langfuse = Langfuse() - prompt_name = create_uuid() - - for i in range(3): - langfuse.create_prompt( - name=prompt_name, - prompt="test prompt " + str(i + 1), - labels=["production"] if i == 1 else [], - ) - - default_prompt_client = langfuse.get_prompt(prompt_name) - assert default_prompt_client.version == 2 - assert default_prompt_client.prompt == "test prompt 2" - assert default_prompt_client.labels == ["production"] - - first_prompt_client = langfuse.get_prompt(prompt_name, version=1) - assert first_prompt_client.version == 1 - assert first_prompt_client.prompt == "test prompt 1" - assert first_prompt_client.labels == [] - - second_prompt_client = langfuse.get_prompt(prompt_name, version=2) - assert second_prompt_client.version == 2 - assert second_prompt_client.prompt == "test prompt 2" - assert second_prompt_client.labels == ["production"] - - third_prompt_client = langfuse.get_prompt(prompt_name, label="latest") - assert third_prompt_client.version == 3 - assert third_prompt_client.prompt == "test prompt 3" - assert third_prompt_client.labels == ["latest"] - - -def test_prompt_end_to_end(): - langfuse = Langfuse(debug=False) - - langfuse.create_prompt( - name="test", - prompt="Hello, {{target}}! I hope you are {{state}}.", - labels=["production"], - config={"temperature": 0.5}, - ) - - prompt = langfuse.get_prompt("test") - - prompt_str = prompt.compile(target="world", state="great") - assert prompt_str == "Hello, world! I hope you are great." - assert prompt.config == {"temperature": 0.5} - - generation = langfuse.start_observation( - as_type="generation", - name="mygen", - input=prompt_str, - prompt=prompt, - ).end() - - # to check that these do not error - generation.update(prompt=prompt) - - langfuse.flush() - - api = get_api() - - trace = api.trace.get(generation.trace_id) - - assert len(trace.observations) == 1 - - generation = trace.observations[0] - assert generation.prompt_id is not None - - observation = api.legacy.observations_v1.get(generation.id) - - assert observation.prompt_id is not None - - -@pytest.fixture -def langfuse(): - from langfuse._client.resource_manager import LangfuseResourceManager - - langfuse_instance = Langfuse() - langfuse_instance.api = Mock() - - if langfuse_instance._resources is None: - langfuse_instance._resources = Mock(spec=LangfuseResourceManager) - langfuse_instance._resources.prompt_cache = PromptCache() - - return langfuse_instance - - -# Fetching a new prompt when nothing in cache -def test_get_fresh_prompt(langfuse): - prompt_name = "test_get_fresh_prompt" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - type="text", - labels=[], - config={}, - tags=[], - ) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result = langfuse.get_prompt(prompt_name, fallback="fallback") - mock_server_call.assert_called_once_with( - prompt_name, - version=None, - label=None, - request_options=None, - ) - - assert result == TextPromptClient(prompt) - - -# Should throw an error if prompt name is unspecified -def test_throw_if_name_unspecified(langfuse): - prompt_name = "" - - with pytest.raises(ValueError) as exc_info: - langfuse.get_prompt(prompt_name) - - assert "Prompt name cannot be empty" in str(exc_info.value) - - -# Should throw an error if nothing in cache and fetch fails -def test_throw_when_failing_fetch_and_no_cache(langfuse): - prompt_name = "failing_fetch_and_no_cache" - - mock_server_call = langfuse.api.prompts.get - mock_server_call.side_effect = Exception("Prompt not found") - - with pytest.raises(Exception) as exc_info: - langfuse.get_prompt(prompt_name) - - assert "Prompt not found" in str(exc_info.value) - - -def test_using_custom_prompt_timeouts(langfuse): - prompt_name = "test_using_custom_prompt_timeouts" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - type="text", - labels=[], - config={}, - tags=[], - ) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result = langfuse.get_prompt( - prompt_name, fallback="fallback", fetch_timeout_seconds=1000 - ) - mock_server_call.assert_called_once_with( - prompt_name, - version=None, - label=None, - request_options={"timeout_in_seconds": 1000}, - ) - - assert result == TextPromptClient(prompt) - - -# Should throw an error if cache_ttl_seconds is passed as positional rather than keyword argument -def test_throw_if_cache_ttl_seconds_positional_argument(langfuse): - prompt_name = "test ttl seconds in positional arg" - ttl_seconds = 20 - - with pytest.raises(TypeError) as exc_info: - langfuse.get_prompt(prompt_name, ttl_seconds) - - assert "positional arguments" in str(exc_info.value) - - -# Should return cached prompt if not expired -def test_get_valid_cached_prompt(langfuse): - prompt_name = "test_get_valid_cached_prompt" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - type="text", - labels=[], - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name, fallback="fallback") - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - result_call_2 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_2 == prompt_client - - -# Should return cached chat prompt if not expired when fetching by label -def test_get_valid_cached_chat_prompt_by_label(langfuse): - prompt_name = "test_get_valid_cached_chat_prompt_by_label" - prompt = Prompt_Chat( - name=prompt_name, - version=1, - prompt=[{"role": "system", "content": "Make me laugh"}], - labels=["test"], - type="chat", - config={}, - tags=[], - ) - prompt_client = ChatPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name, label="test") - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - result_call_2 = langfuse.get_prompt(prompt_name, label="test") - assert mock_server_call.call_count == 1 - assert result_call_2 == prompt_client - - -# Should return cached chat prompt if not expired when fetching by version -def test_get_valid_cached_chat_prompt_by_version(langfuse): - prompt_name = "test_get_valid_cached_chat_prompt_by_version" - prompt = Prompt_Chat( - name=prompt_name, - version=1, - prompt=[{"role": "system", "content": "Make me laugh"}], - labels=["test"], - type="chat", - config={}, - tags=[], - ) - prompt_client = ChatPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name, version=1) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - result_call_2 = langfuse.get_prompt(prompt_name, version=1) - assert mock_server_call.call_count == 1 - assert result_call_2 == prompt_client - - -# Should return cached chat prompt if fetching the default prompt or the 'production' labeled one -def test_get_valid_cached_production_chat_prompt(langfuse): - prompt_name = "test_get_valid_cached_production_chat_prompt" - prompt = Prompt_Chat( - name=prompt_name, - version=1, - prompt=[{"role": "system", "content": "Make me laugh"}], - labels=["test"], - type="chat", - config={}, - tags=[], - ) - prompt_client = ChatPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - result_call_2 = langfuse.get_prompt(prompt_name, label="production") - assert mock_server_call.call_count == 1 - assert result_call_2 == prompt_client - - -# Should return cached chat prompt if not expired -def test_get_valid_cached_chat_prompt(langfuse): - prompt_name = "test_get_valid_cached_chat_prompt" - prompt = Prompt_Chat( - name=prompt_name, - version=1, - prompt=[{"role": "system", "content": "Make me laugh"}], - labels=[], - type="chat", - config={}, - tags=[], - ) - prompt_client = ChatPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - result_call_2 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_2 == prompt_client - - -# Should refetch and return new prompt if cached one is expired according to custom TTL -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_get_fresh_prompt_when_expired_cache_custom_ttl(mock_time, langfuse: Langfuse): - mock_time.return_value = 0 - ttl_seconds = 20 - - prompt_name = "test_get_fresh_prompt_when_expired_cache_custom_ttl" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - config={"temperature": 0.9}, - labels=[], - type="text", - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=ttl_seconds) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - # Set time to just BEFORE cache expiry - mock_time.return_value = ttl_seconds - 1 - - result_call_2 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 # No new call - assert result_call_2 == prompt_client - - # Set time to just AFTER cache expiry - mock_time.return_value = ttl_seconds + 1 - - result_call_3 = langfuse.get_prompt(prompt_name) - - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - assert mock_server_call.call_count == 2 # New call - assert result_call_3 == prompt_client - - -# Should disable caching when cache_ttl_seconds is set to 0 -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_disable_caching_when_ttl_zero(mock_time, langfuse: Langfuse): - mock_time.return_value = 0 - prompt_name = "test_disable_caching_when_ttl_zero" - - # Initial prompt - prompt1 = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - - # Updated prompts - prompt2 = Prompt_Text( - name=prompt_name, - version=2, - prompt="Tell me a joke", - labels=[], - type="text", - config={}, - tags=[], - ) - prompt3 = Prompt_Text( - name=prompt_name, - version=3, - prompt="Share a funny story", - labels=[], - type="text", - config={}, - tags=[], - ) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.side_effect = [prompt1, prompt2, prompt3] - - # First call - result1 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=0) - assert mock_server_call.call_count == 1 - assert result1 == TextPromptClient(prompt1) - - # Second call - result2 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=0) - assert mock_server_call.call_count == 2 - assert result2 == TextPromptClient(prompt2) - - # Third call - result3 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=0) - assert mock_server_call.call_count == 3 - assert result3 == TextPromptClient(prompt3) - - # Verify that all results are different - assert result1 != result2 != result3 - - -# Should return stale prompt immediately if cached one is expired according to default TTL and add to refresh promise map -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_get_stale_prompt_when_expired_cache_default_ttl(mock_time, langfuse: Langfuse): - import logging - - logging.basicConfig(level=logging.DEBUG) - mock_time.return_value = 0 - - prompt_name = "test_get_stale_prompt_when_expired_cache_default_ttl" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - # Update the version of the returned mocked prompt - updated_prompt = Prompt_Text( - name=prompt_name, - version=2, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - mock_server_call.return_value = updated_prompt - - # Set time to just AFTER cache expiry - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 - - stale_result = langfuse.get_prompt(prompt_name) - assert stale_result == prompt_client - - # Ensure that only one refresh is triggered despite multiple calls - # Cannot check for value as the prompt might have already been updated - langfuse.get_prompt(prompt_name) - langfuse.get_prompt(prompt_name) - langfuse.get_prompt(prompt_name) - langfuse.get_prompt(prompt_name) - - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - assert mock_server_call.call_count == 2 # Only one new call to server - - # Check that the prompt has been updated after refresh - updated_result = langfuse.get_prompt(prompt_name) - assert updated_result.version == 2 - assert updated_result == TextPromptClient(updated_prompt) - - -# Should refetch and return new prompt if cached one is expired according to default TTL -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_get_fresh_prompt_when_expired_cache_default_ttl(mock_time, langfuse: Langfuse): - mock_time.return_value = 0 - - prompt_name = "test_get_fresh_prompt_when_expired_cache_default_ttl" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - # Set time to just BEFORE cache expiry - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS - 1 - - result_call_2 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 # No new call - assert result_call_2 == prompt_client - - # Set time to just AFTER cache expiry - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 - - result_call_3 = langfuse.get_prompt(prompt_name) - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - assert mock_server_call.call_count == 2 # New call - assert result_call_3 == prompt_client - - -# Should return expired prompt if refetch fails -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_get_expired_prompt_when_failing_fetch(mock_time, langfuse: Langfuse): - mock_time.return_value = 0 - - prompt_name = "test_get_expired_prompt_when_failing_fetch" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - # Set time to just AFTER cache expiry - mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 - - mock_server_call.side_effect = Exception("Server error") - - result_call_2 = langfuse.get_prompt(prompt_name, max_retries=1) - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - assert mock_server_call.call_count == 3 - assert result_call_2 == prompt_client - - -@patch.object(PromptCacheItem, "get_epoch_seconds") -def test_evict_prompt_cache_entry_when_refresh_returns_not_found( - mock_time, langfuse: Langfuse -) -> None: - mock_time.return_value = 0 - - prompt_name = "test_evict_prompt_cache_entry_when_refresh_returns_not_found" - ttl_seconds = 5 - fallback_prompt = "fallback text prompt" - - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - cache_key = PromptCache.generate_cache_key(prompt_name, version=None, label=None) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - initial_result = langfuse.get_prompt( - prompt_name, - cache_ttl_seconds=ttl_seconds, - max_retries=0, - ) - assert initial_result == prompt_client - assert langfuse._resources.prompt_cache.get(cache_key) is not None - - # Expire cache entry and trigger background refresh - mock_time.return_value = ttl_seconds + 1 - - def raise_not_found(*_args: object, **_kwargs: object) -> None: - raise NotFoundError({"message": "Prompt not found"}) - - mock_server_call.side_effect = raise_not_found - - stale_result = langfuse.get_prompt( - prompt_name, - cache_ttl_seconds=ttl_seconds, - max_retries=0, - ) - assert stale_result == prompt_client - - while True: - if langfuse._resources.prompt_cache._task_manager.active_tasks() == 0: - break - sleep(0.1) - - assert langfuse._resources.prompt_cache.get(cache_key) is None - - fallback_result = langfuse.get_prompt( - prompt_name, - cache_ttl_seconds=ttl_seconds, - fallback=fallback_prompt, - max_retries=0, - ) - assert fallback_result.is_fallback - assert fallback_result.prompt == fallback_prompt - - -# Should fetch new prompt if version changes -def test_get_fresh_prompt_when_version_changes(langfuse: Langfuse): - prompt_name = "test_get_fresh_prompt_when_version_changes" - prompt = Prompt_Text( - name=prompt_name, - version=1, - prompt="Make me laugh", - labels=[], - type="text", - config={}, - tags=[], - ) - prompt_client = TextPromptClient(prompt) - - mock_server_call = langfuse.api.prompts.get - mock_server_call.return_value = prompt - - result_call_1 = langfuse.get_prompt(prompt_name, version=1) - assert mock_server_call.call_count == 1 - assert result_call_1 == prompt_client - - version_changed_prompt = Prompt_Text( - name=prompt_name, - version=2, - labels=[], - prompt="Make me laugh", - type="text", - config={}, - tags=[], - ) - version_changed_prompt_client = TextPromptClient(version_changed_prompt) - mock_server_call.return_value = version_changed_prompt - - result_call_2 = langfuse.get_prompt(prompt_name, version=2) - assert mock_server_call.call_count == 2 - assert result_call_2 == version_changed_prompt_client - - -def test_do_not_return_fallback_if_fetch_success(): - langfuse = Langfuse() - prompt_name = create_uuid() - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt="test prompt", - labels=["production"], - ) - - second_prompt_client = langfuse.get_prompt(prompt_name, fallback="fallback") - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.config == second_prompt_client.config - assert prompt_client.config == {} - - -def test_fallback_text_prompt(): - langfuse = Langfuse() - - fallback_text_prompt = "this is a fallback text prompt with {{variable}}" - - # Should throw an error if prompt not found and no fallback provided - with pytest.raises(Exception): - langfuse.get_prompt("nonexistent_prompt") - - prompt = langfuse.get_prompt("nonexistent_prompt", fallback=fallback_text_prompt) - - assert prompt.prompt == fallback_text_prompt - assert ( - prompt.compile(variable="value") == "this is a fallback text prompt with value" - ) - - -def test_fallback_chat_prompt(): - langfuse = Langfuse() - fallback_chat_prompt = [ - {"role": "system", "content": "fallback system"}, - {"role": "user", "content": "fallback user name {{name}}"}, - ] - - # Should throw an error if prompt not found and no fallback provided - with pytest.raises(Exception): - langfuse.get_prompt("nonexistent_chat_prompt", type="chat") - - prompt = langfuse.get_prompt( - "nonexistent_chat_prompt", type="chat", fallback=fallback_chat_prompt - ) - - # Check that the prompt structure contains the fallback data (allowing for internal formatting) - assert len(prompt.prompt) == len(fallback_chat_prompt) - assert all(msg["type"] == "message" for msg in prompt.prompt) - assert prompt.prompt[0]["role"] == "system" - assert prompt.prompt[0]["content"] == "fallback system" - assert prompt.prompt[1]["role"] == "user" - assert prompt.prompt[1]["content"] == "fallback user name {{name}}" - assert prompt.compile(name="Jane") == [ - {"role": "system", "content": "fallback system"}, - {"role": "user", "content": "fallback user name Jane"}, - ] - - -def test_do_not_link_observation_if_fallback(): - langfuse = Langfuse() - - fallback_text_prompt = "this is a fallback text prompt with {{variable}}" - - # Should throw an error if prompt not found and no fallback provided - with pytest.raises(Exception): - langfuse.get_prompt("nonexistent_prompt") - - prompt = langfuse.get_prompt("nonexistent_prompt", fallback=fallback_text_prompt) - - generation = langfuse.start_observation( - as_type="generation", - name="mygen", - prompt=prompt, - input="this is a test input", - ).end() - langfuse.flush() - - api = get_api() - trace = api.trace.get(generation.trace_id) - - assert len(trace.observations) == 1 - assert trace.observations[0].prompt_id is None - - -def test_variable_names_on_content_with_variable_names(): - langfuse = Langfuse() - - prompt_client = langfuse.create_prompt( - name="test_variable_names_1", - prompt="test prompt with var names {{ var1 }} {{ var2 }}", - labels=["production"], - type="text", - ) - - second_prompt_client = langfuse.get_prompt("test_variable_names_1") - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - var_names = second_prompt_client.variables - - assert var_names == ["var1", "var2"] - - -def test_variable_names_on_content_with_no_variable_names(): - langfuse = Langfuse() - - prompt_client = langfuse.create_prompt( - name="test_variable_names_2", - prompt="test prompt with no var names", - labels=["production"], - type="text", - ) - - second_prompt_client = langfuse.get_prompt("test_variable_names_2") - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - var_names = second_prompt_client.variables - - assert var_names == [] - - -def test_variable_names_on_content_with_variable_names_chat_messages(): - langfuse = Langfuse() - - prompt_client = langfuse.create_prompt( - name="test_variable_names_3", - prompt=[ - { - "role": "system", - "content": "test prompt with template vars {{ var1 }} {{ var2 }}", - }, - {"role": "user", "content": "test prompt 2 with template vars {{ var3 }}"}, - ], - labels=["production"], - type="chat", - ) - - second_prompt_client = langfuse.get_prompt("test_variable_names_3") - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - var_names = second_prompt_client.variables - - assert var_names == ["var1", "var2", "var3"] - - -def test_variable_names_on_content_with_no_variable_names_chat_messages(): - langfuse = Langfuse() - prompt_name = "test_variable_names_on_content_with_no_variable_names_chat_messages" - - prompt_client = langfuse.create_prompt( - name=prompt_name, - prompt=[ - {"role": "system", "content": "test prompt with no template vars"}, - {"role": "user", "content": "test prompt 2 with no template vars"}, - ], - labels=["production"], - type="chat", - ) - - second_prompt_client = langfuse.get_prompt(prompt_name) - - assert prompt_client.name == second_prompt_client.name - assert prompt_client.version == second_prompt_client.version - assert prompt_client.prompt == second_prompt_client.prompt - assert prompt_client.labels == ["production", "latest"] - - var_names = second_prompt_client.variables - - assert var_names == [] - - -def test_update_prompt(): - langfuse = Langfuse() - prompt_name = create_uuid() - - # Create initial prompt - langfuse.create_prompt( - name=prompt_name, - prompt="test prompt", - labels=["production"], - ) - - # Update prompt labels - updated_prompt = langfuse.update_prompt( - name=prompt_name, - version=1, - new_labels=["john", "doe"], - ) - - # Fetch prompt after update (should be invalidated) - fetched_prompt = langfuse.get_prompt(prompt_name) - - # Verify the fetched prompt matches the updated values - assert fetched_prompt.name == prompt_name - assert fetched_prompt.version == 1 - print(f"Fetched prompt labels: {fetched_prompt.labels}") - print(f"Updated prompt labels: {updated_prompt.labels}") - - # production was set by the first call, latest is managed and set by Langfuse - expected_labels = sorted(["latest", "doe", "production", "john"]) - assert sorted(fetched_prompt.labels) == expected_labels - assert sorted(updated_prompt.labels) == expected_labels - - -def test_update_prompt_in_folder(): - langfuse = Langfuse() - prompt_name = f"some-folder/{create_uuid()}" - - # Create initial prompt - langfuse.create_prompt( - name=prompt_name, - prompt="test prompt", - labels=["production"], - ) - - old_prompt_obj = langfuse.get_prompt(prompt_name) - - updated_prompt = langfuse.update_prompt( - name=old_prompt_obj.name, - version=old_prompt_obj.version, - new_labels=["john", "doe"], - ) - - # Fetch prompt after update (should be invalidated) - fetched_prompt = langfuse.get_prompt(prompt_name) - - # Verify the fetched prompt matches the updated values - assert fetched_prompt.name == prompt_name - assert fetched_prompt.version == 1 - print(f"Fetched prompt labels: {fetched_prompt.labels}") - print(f"Updated prompt labels: {updated_prompt.labels}") - - # production was set by the first call, latest is managed and set by Langfuse - expected_labels = sorted(["latest", "doe", "production", "john"]) - assert sorted(fetched_prompt.labels) == expected_labels - assert sorted(updated_prompt.labels) == expected_labels diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/test_additional_headers_simple.py b/tests/unit/test_additional_headers_simple.py similarity index 100% rename from tests/test_additional_headers_simple.py rename to tests/unit/test_additional_headers_simple.py diff --git a/tests/unit/test_e2e_sharding.py b/tests/unit/test_e2e_sharding.py new file mode 100644 index 000000000..a1ec84e58 --- /dev/null +++ b/tests/unit/test_e2e_sharding.py @@ -0,0 +1,54 @@ +import importlib.util +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +SCRIPT_PATH = REPO_ROOT / "scripts" / "select_e2e_shard.py" + + +def load_shard_script(): + spec = importlib.util.spec_from_file_location("select_e2e_shard", SCRIPT_PATH) + if spec is None or spec.loader is None: + raise AssertionError(f"Unable to load shard selector from {SCRIPT_PATH}") + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_e2e_shards_cover_all_files_once(): + shard_script = load_shard_script() + + all_files = sorted( + path.relative_to(REPO_ROOT).as_posix() + for path in (REPO_ROOT / "tests" / "e2e").glob("test_*.py") + ) + + shards, shard_loads = shard_script.assign_shards( + shard_script.discover_e2e_files(), shard_count=2 + ) + + assert len(shards) == 2 + assert set(shards[0]).isdisjoint(shards[1]) + assert sorted([path for shard in shards for path in shard]) == all_files + assert all(load > 0 for load in shard_loads) + + +def test_unknown_file_weight_falls_back_to_test_count(tmp_path: Path): + shard_script = load_shard_script() + + test_file = tmp_path / "test_future_suite.py" + test_file.write_text( + "\n".join( + [ + "def test_one():", + " pass", + "", + "async def test_two():", + " pass", + ] + ), + encoding="utf-8", + ) + + assert shard_script.count_test_functions(test_file) == 2 + assert shard_script.estimate_weight(test_file) == 2 diff --git a/tests/unit/test_e2e_support.py b/tests/unit/test_e2e_support.py new file mode 100644 index 000000000..8320bd2fe --- /dev/null +++ b/tests/unit/test_e2e_support.py @@ -0,0 +1,173 @@ +from types import SimpleNamespace + +from langfuse.api.commons.errors.not_found_error import NotFoundError +from tests.support.api_wrapper import LangfuseAPI as SupportLangfuseAPI +from tests.support.retry import retry_until_ready +from tests.support.utils import get_api, wait_for_trace + + +def test_get_api_retries_not_found(monkeypatch): + monkeypatch.setattr("tests.support.retry.sleep", lambda _: None) + + attempts = {"count": 0} + + class FakeTraceService: + def get(self, trace_id): + attempts["count"] += 1 + + if attempts["count"] < 3: + raise NotFoundError( + body={ + "error": "LangfuseNotFoundError", + "message": f"Trace {trace_id} not found within authorized project", + } + ) + + return {"id": trace_id} + + class FakeClient: + trace = FakeTraceService() + + monkeypatch.setattr("tests.support.utils.LangfuseAPI", lambda **_: FakeClient()) + + trace = get_api().trace.get("trace-123") + + assert trace == {"id": "trace-123"} + assert attempts["count"] == 3 + + +def test_get_api_retries_filtered_lists(monkeypatch): + monkeypatch.setattr("tests.support.retry.sleep", lambda _: None) + + attempts = {"count": 0} + + class FakeTraceService: + def list(self, **kwargs): + attempts["count"] += 1 + + if attempts["count"] < 3: + return SimpleNamespace(data=[]) + + return SimpleNamespace(data=[kwargs["name"]]) + + class FakeClient: + trace = FakeTraceService() + + monkeypatch.setattr("tests.support.utils.LangfuseAPI", lambda **_: FakeClient()) + + response = get_api().trace.list(name="ready-trace") + + assert response.data == ["ready-trace"] + assert attempts["count"] == 3 + + +def test_get_api_retry_can_be_disabled(monkeypatch): + attempts = {"count": 0} + + class FakeTraceService: + def list(self, **kwargs): + attempts["count"] += 1 + return SimpleNamespace(data=[]) + + class FakeClient: + trace = FakeTraceService() + + monkeypatch.setattr("tests.support.utils.LangfuseAPI", lambda **_: FakeClient()) + + response = get_api(retry=False).trace.list(name="missing-trace") + + assert response.data == [] + assert attempts["count"] == 1 + + +def test_raw_api_wrapper_retries_not_found_payload(monkeypatch): + monkeypatch.setattr("tests.support.retry.sleep", lambda _: None) + + attempts = {"count": 0} + + class FakeResponse: + def __init__(self, status_code, payload): + self.status_code = status_code + self._payload = payload + self.headers = {} + + def json(self): + return self._payload + + def fake_get(*args, **kwargs): + attempts["count"] += 1 + + if attempts["count"] < 3: + return FakeResponse( + 404, + { + "error": "LangfuseNotFoundError", + "message": "Trace trace-123 not found within authorized project", + }, + ) + + return FakeResponse(200, {"id": "trace-123", "observations": []}) + + monkeypatch.setattr("tests.support.api_wrapper.httpx.get", fake_get) + + api = SupportLangfuseAPI(username="user", password="pass", base_url="http://test") + trace = api.get_trace("trace-123") + + assert trace["id"] == "trace-123" + assert attempts["count"] == 3 + + +def test_wait_for_trace_retries_until_predicate_matches(monkeypatch): + monkeypatch.setattr("tests.support.retry.sleep", lambda _: None) + + attempts = {"count": 0} + + class FakeTraceService: + def get(self, trace_id): + attempts["count"] += 1 + return {"id": trace_id, "observations": [1] * attempts["count"]} + + class FakeClient: + trace = FakeTraceService() + + monkeypatch.setattr("tests.support.utils.LangfuseAPI", lambda **_: FakeClient()) + + trace = wait_for_trace( + "trace-123", is_result_ready=lambda trace: len(trace["observations"]) == 3 + ) + + assert trace["id"] == "trace-123" + assert len(trace["observations"]) == 3 + assert attempts["count"] == 3 + + +def test_retry_until_ready_clears_stale_error_after_success(monkeypatch): + monkeypatch.setattr("tests.support.retry.sleep", lambda _: None) + + monotonic_values = iter([0.0, 0.0, 0.05, 0.06, 0.11, 0.11]) + monkeypatch.setattr("tests.support.retry.monotonic", lambda: next(monotonic_values)) + + attempts = {"count": 0} + + def operation(): + attempts["count"] += 1 + + if attempts["count"] == 1: + raise NotFoundError( + body={ + "error": "LangfuseNotFoundError", + "message": "Trace trace-123 not found within authorized project", + } + ) + + return {"id": "trace-123", "attempt": attempts["count"], "observations": []} + + trace = retry_until_ready( + operation, + is_result_ready=lambda _: False, + timeout_seconds=0.1, + interval_seconds=0, + ) + + assert trace["id"] == "trace-123" + assert trace["attempt"] == 3 diff --git a/tests/test_error_logging.py b/tests/unit/test_error_logging.py similarity index 100% rename from tests/test_error_logging.py rename to tests/unit/test_error_logging.py diff --git a/tests/test_error_parsing.py b/tests/unit/test_error_parsing.py similarity index 100% rename from tests/test_error_parsing.py rename to tests/unit/test_error_parsing.py diff --git a/tests/test_initialization.py b/tests/unit/test_initialization.py similarity index 100% rename from tests/test_initialization.py rename to tests/unit/test_initialization.py diff --git a/tests/test_json.py b/tests/unit/test_json.py similarity index 100% rename from tests/test_json.py rename to tests/unit/test_json.py diff --git a/tests/unit/test_langchain.py b/tests/unit/test_langchain.py new file mode 100644 index 000000000..b4c1ba2ee --- /dev/null +++ b/tests/unit/test_langchain.py @@ -0,0 +1,168 @@ +from unittest.mock import patch + +import pytest +from langchain.messages import HumanMessage +from langchain_core.messages import AIMessage +from langchain_core.output_parsers import StrOutputParser +from langchain_core.outputs import ChatGeneration, ChatResult, Generation, LLMResult +from langchain_core.prompts import ChatPromptTemplate +from langchain_openai import ChatOpenAI, OpenAI + +from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse.langchain import CallbackHandler + + +def _assert_parent_child(parent_span, child_span) -> None: + assert child_span.parent is not None + assert child_span.parent.span_id == parent_span.context.span_id + + +def test_chat_model_callback_exports_generation_span( + langfuse_memory_client, get_span, json_attr +): + response = ChatResult( + generations=[ + ChatGeneration(message=AIMessage(content="bonjour"), text="bonjour") + ], + llm_output={ + "token_usage": { + "prompt_tokens": 4, + "completion_tokens": 2, + "total_tokens": 6, + }, + "model_name": "gpt-4o-mini", + }, + ) + + with patch.object(ChatOpenAI, "_generate", return_value=response): + handler = CallbackHandler() + + with langfuse_memory_client.start_as_current_observation(name="parent"): + ChatOpenAI(api_key="test", temperature=0).invoke( + [HumanMessage(content="hello")], + config={"callbacks": [handler]}, + ) + + langfuse_memory_client.flush() + parent_span = get_span("parent") + generation_span = get_span("ChatOpenAI") + + _assert_parent_child(parent_span, generation_span) + assert ( + generation_span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] + == "generation" + ) + assert json_attr(generation_span, LangfuseOtelSpanAttributes.OBSERVATION_INPUT) == [ + {"role": "user", "content": "hello"} + ] + assert json_attr( + generation_span, LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT + ) == { + "role": "assistant", + "content": "bonjour", + } + assert ( + generation_span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_MODEL] + == "gpt-4o-mini" + ) + assert json_attr( + generation_span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS + ) == { + "prompt_tokens": 4, + "completion_tokens": 2, + "total_tokens": 6, + } + + +def test_llm_callback_exports_generation_span(langfuse_memory_client, get_span): + response = LLMResult( + generations=[[Generation(text="sockzilla")]], + llm_output={ + "token_usage": { + "prompt_tokens": 7, + "completion_tokens": 3, + "total_tokens": 10, + }, + "model_name": "gpt-4o-mini-instruct", + }, + ) + + with patch.object(OpenAI, "_generate", return_value=response): + handler = CallbackHandler() + + with langfuse_memory_client.start_as_current_observation(name="parent"): + OpenAI(api_key="test", temperature=0).invoke( + "name a sock company", + config={"callbacks": [handler], "run_name": "sock-name"}, + ) + + langfuse_memory_client.flush() + span = get_span("sock-name") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "sockzilla" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_MODEL] + == "gpt-4o-mini-instruct" + ) + + +def test_lcel_chain_exports_intermediate_chain_spans( + langfuse_memory_client, get_span, find_spans +): + response = ChatResult( + generations=[ + ChatGeneration( + message=AIMessage(content="knock knock"), + text="knock knock", + ) + ], + llm_output={ + "token_usage": { + "prompt_tokens": 4, + "completion_tokens": 2, + "total_tokens": 6, + }, + "model_name": "gpt-4o-mini", + }, + ) + + with patch.object(ChatOpenAI, "_generate", return_value=response): + handler = CallbackHandler() + prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}") + chain = prompt | ChatOpenAI(api_key="test", temperature=0) | StrOutputParser() + + with langfuse_memory_client.start_as_current_observation(name="parent"): + result = chain.invoke({"topic": "otters"}, config={"callbacks": [handler]}) + + assert result == "knock knock" + + langfuse_memory_client.flush() + sequence_span = get_span("RunnableSequence") + prompt_span = get_span("ChatPromptTemplate") + generation_span = get_span("ChatOpenAI") + parser_span = get_span("StrOutputParser") + + _assert_parent_child(sequence_span, prompt_span) + _assert_parent_child(sequence_span, generation_span) + _assert_parent_child(sequence_span, parser_span) + assert len(find_spans("ChatOpenAI")) == 1 + + +def test_chat_model_error_marks_generation_error(langfuse_memory_client, get_span): + with patch.object(ChatOpenAI, "_generate", side_effect=RuntimeError("boom")): + handler = CallbackHandler() + + with langfuse_memory_client.start_as_current_observation(name="parent"): + with pytest.raises(RuntimeError, match="boom"): + ChatOpenAI(api_key="test", temperature=0).invoke( + [HumanMessage(content="hello")], + config={"callbacks": [handler]}, + ) + + langfuse_memory_client.flush() + span = get_span("ChatOpenAI") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert ( + "boom" in span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + ) diff --git a/tests/test_logger.py b/tests/unit/test_logger.py similarity index 100% rename from tests/test_logger.py rename to tests/unit/test_logger.py diff --git a/tests/test_media.py b/tests/unit/test_media.py similarity index 69% rename from tests/test_media.py rename to tests/unit/test_media.py index 6c095ece1..63df03920 100644 --- a/tests/test_media.py +++ b/tests/unit/test_media.py @@ -1,14 +1,10 @@ import base64 -import re from types import SimpleNamespace from unittest.mock import Mock -from uuid import uuid4 import pytest -from langfuse._client.client import Langfuse from langfuse.media import LangfuseMedia -from tests.utils import get_api # Test data SAMPLE_JPEG_BYTES = b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00" @@ -143,61 +139,3 @@ def test_resolve_media_references_uses_configured_httpx_client(): httpx_client.get.assert_called_once_with( "https://example.com/test.jpg", timeout=fetch_timeout_seconds ) - - -def test_replace_media_reference_string_in_object(): - # Create test audio file - audio_file = "static/joke_prompt.wav" - with open(audio_file, "rb") as f: - mock_audio_bytes = f.read() - - # Create Langfuse client and trace with media - langfuse = Langfuse() - - mock_trace_name = f"test-trace-with-audio-{uuid4()}" - base64_audio = base64.b64encode(mock_audio_bytes).decode() - - span = langfuse.start_observation( - name=mock_trace_name, - metadata={ - "context": { - "nested": LangfuseMedia( - base64_data_uri=f"data:audio/wav;base64,{base64_audio}" - ) - } - }, - ).end() - - langfuse.flush() - - # Verify media reference string format - fetched_trace = get_api().trace.get(span.trace_id) - media_ref = fetched_trace.observations[0].metadata["context"]["nested"] - assert re.match( - r"^@@@langfuseMedia:type=audio/wav\|id=.+\|source=base64_data_uri@@@$", - media_ref, - ) - - # Resolve media references back to base64 - resolved_obs = langfuse.resolve_media_references( - obj=fetched_trace.observations[0], resolve_with="base64_data_uri" - ) - - # Verify resolved base64 matches original - expected_base64 = f"data:audio/wav;base64,{base64_audio}" - assert resolved_obs["metadata"]["context"]["nested"] == expected_base64 - - # Create second trace reusing the media reference - span2 = langfuse.start_observation( - name=f"2-{mock_trace_name}", - metadata={"context": {"nested": resolved_obs["metadata"]["context"]["nested"]}}, - ).end() - - langfuse.flush() - - # Verify second trace has same media reference - fetched_trace2 = get_api().trace.get(span2.trace_id) - assert ( - fetched_trace2.observations[0].metadata["context"]["nested"] - == fetched_trace.observations[0].metadata["context"]["nested"] - ) diff --git a/tests/test_media_manager.py b/tests/unit/test_media_manager.py similarity index 100% rename from tests/test_media_manager.py rename to tests/unit/test_media_manager.py diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py new file mode 100644 index 000000000..6ef51ff54 --- /dev/null +++ b/tests/unit/test_openai.py @@ -0,0 +1,238 @@ +from types import SimpleNamespace +from unittest.mock import patch + +import pytest + +from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse.openai import openai as lf_openai + + +def test_chat_completion_exports_generation_span( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.OpenAI(api_key="test") + response = SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + message=SimpleNamespace( + role="assistant", + content="2", + function_call=None, + tool_calls=None, + audio=None, + ) + ) + ], + usage=SimpleNamespace(prompt_tokens=3, completion_tokens=1, total_tokens=4), + ) + + with patch.object(openai_client.chat.completions, "_post", return_value=response): + result = openai_client.chat.completions.create( + name="unit-openai-chat", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + metadata={"suite": "unit"}, + ) + + assert result is response + + langfuse_memory_client.flush() + span = get_span("unit-openai-chat") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] == "generation" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_MODEL] == "gpt-4o-mini" + ) + assert span.attributes["langfuse.observation.metadata.suite"] == "unit" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_INPUT) == [ + {"role": "user", "content": "1 + 1 = ?"} + ] + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT) == { + "role": "assistant", + "content": "2", + } + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_MODEL_PARAMETERS) == { + "temperature": 0, + "max_tokens": "Infinity", + "top_p": 1, + "frequency_penalty": 0, + "presence_penalty": 0, + } + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 3, + "completion_tokens": 1, + "total_tokens": 4, + } + + +def test_streaming_chat_completion_exports_ttft( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.OpenAI(api_key="test") + usage = SimpleNamespace(prompt_tokens=3, completion_tokens=1, total_tokens=4) + + def fake_stream(): + yield SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role="assistant", + content="2", + function_call=None, + tool_calls=None, + ), + finish_reason=None, + ) + ], + usage=None, + ) + yield SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role=None, + content=None, + function_call=None, + tool_calls=None, + ), + finish_reason="stop", + ) + ], + usage=usage, + ) + + with patch.object( + openai_client.chat.completions, "_post", return_value=fake_stream() + ): + stream = openai_client.chat.completions.create( + name="unit-openai-stream", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + chunks = list(stream) + + assert len(chunks) == 2 + + langfuse_memory_client.flush() + span = get_span("unit-openai-stream") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME] + is not None + ) + assert span.attributes["langfuse.observation.metadata.finish_reason"] == "stop" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 3, + "completion_tokens": 1, + "total_tokens": 4, + } + + +def test_chat_completion_error_marks_generation_error(langfuse_memory_client, get_span): + openai_client = lf_openai.OpenAI(api_key="test") + + with patch.object( + openai_client.chat.completions, + "_post", + side_effect=RuntimeError("boom"), + ): + with pytest.raises(RuntimeError, match="boom"): + openai_client.chat.completions.create( + name="unit-openai-error", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "explode"}], + temperature=0, + ) + + langfuse_memory_client.flush() + span = get_span("unit-openai-error") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert ( + "boom" in span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + ) + assert LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT not in span.attributes + + +@pytest.mark.asyncio +async def test_async_chat_completion_exports_generation_span( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.AsyncOpenAI(api_key="test") + response = SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + message=SimpleNamespace( + role="assistant", + content="async result", + function_call=None, + tool_calls=None, + audio=None, + ) + ) + ], + usage=SimpleNamespace(prompt_tokens=5, completion_tokens=2, total_tokens=7), + ) + + with patch.object(openai_client.chat.completions, "_post", return_value=response): + result = await openai_client.chat.completions.create( + name="unit-openai-async", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "hello"}], + temperature=0, + ) + + assert result is response + + langfuse_memory_client.flush() + span = get_span("unit-openai-async") + + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT) == { + "role": "assistant", + "content": "async result", + } + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 5, + "completion_tokens": 2, + "total_tokens": 7, + } + + +def test_embedding_exports_dimensions_and_count( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.OpenAI(api_key="test") + response = SimpleNamespace( + model="text-embedding-3-small", + data=[SimpleNamespace(embedding=[0.1, 0.2, 0.3])], + usage=SimpleNamespace(prompt_tokens=2, total_tokens=2), + ) + + with patch.object(openai_client.embeddings, "_post", return_value=response): + result = openai_client.embeddings.create( + name="unit-openai-embedding", + model="text-embedding-3-small", + input="hello world", + ) + + assert result is response + + langfuse_memory_client.flush() + span = get_span("unit-openai-embedding") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] == "embedding" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT) == { + "dimensions": 3, + "count": 1, + } + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "input": 2 + } diff --git a/tests/test_openai_prompt_extraction.py b/tests/unit/test_openai_prompt_extraction.py similarity index 100% rename from tests/test_openai_prompt_extraction.py rename to tests/unit/test_openai_prompt_extraction.py diff --git a/tests/test_otel.py b/tests/unit/test_otel.py similarity index 99% rename from tests/test_otel.py rename to tests/unit/test_otel.py index b4b985780..e7eb74280 100644 --- a/tests/test_otel.py +++ b/tests/unit/test_otel.py @@ -54,10 +54,17 @@ class TestOTelBase: @pytest.fixture(scope="function", autouse=True) def cleanup_otel(self): """Reset OpenTelemetry state between tests.""" - original_provider = trace_api.get_tracer_provider() + from opentelemetry.util._once import Once + + trace_api._TRACER_PROVIDER = None + trace_api._PROXY_TRACER_PROVIDER = trace_api.ProxyTracerProvider() + trace_api._TRACER_PROVIDER_SET_ONCE = Once() + yield - trace_api.set_tracer_provider(original_provider) LangfuseResourceManager.reset() + trace_api._TRACER_PROVIDER = None + trace_api._PROXY_TRACER_PROVIDER = trace_api.ProxyTracerProvider() + trace_api._TRACER_PROVIDER_SET_ONCE = Once() @pytest.fixture def memory_exporter(self): @@ -97,7 +104,7 @@ def mock_init(self, **kwargs): self, span_exporter=memory_exporter, max_export_batch_size=512, - schedule_delay_millis=5000, + schedule_delay_millis=1, ) monkeypatch.setattr( @@ -1870,7 +1877,7 @@ def update_random_metadata(thread_id): update = random.choice(updates) # Sleep a tiny bit to simulate work and increase chances of thread interleaving - time.sleep(random.uniform(0.001, 0.01)) + time.sleep(random.uniform(0.0005, 0.001)) # Apply the update to current_metadata (in a real system, this would update OTEL span) with metadata_lock: @@ -2001,7 +2008,7 @@ def mock_processor_init(self, **kwargs): self, span_exporter=exporter, max_export_batch_size=512, - schedule_delay_millis=5000, + schedule_delay_millis=1, ) monkeypatch.setattr( @@ -2118,7 +2125,7 @@ def create_spans_project1(): metadata={"project": "project1", "index": i}, ) # Small sleep to ensure overlap with other thread - time.sleep(0.01) + time.sleep(0.001) span.end() def create_spans_project2(): @@ -2128,7 +2135,7 @@ def create_spans_project2(): metadata={"project": "project2", "index": i}, ) # Small sleep to ensure overlap with other thread - time.sleep(0.01) + time.sleep(0.001) span.end() # Start threads @@ -2378,7 +2385,7 @@ def mock_processor_init(self, **kwargs): self, span_exporter=exporter, max_export_batch_size=512, - schedule_delay_millis=5000, + schedule_delay_millis=1, ) monkeypatch.setattr( @@ -2757,7 +2764,7 @@ async def async_task(parent_span, task_id): child_span = parent_span.start_observation(name=f"async-task-{task_id}") # Simulate async work - await asyncio.sleep(0.1) + await asyncio.sleep(0.01) # Update span with results child_span.update( @@ -2948,7 +2955,7 @@ async def test_span_metadata_updates_in_async_context( # Define async tasks that update different parts of metadata async def update_temperature(): - await asyncio.sleep(0.1) # Simulate some async work + await asyncio.sleep(0.01) # Simulate some async work main_span.update( metadata={ "llm_config": { @@ -2960,7 +2967,7 @@ async def update_temperature(): ) async def update_model(): - await asyncio.sleep(0.05) # Simulate some async work + await asyncio.sleep(0.005) # Simulate some async work main_span.update( metadata={ "llm_config": { @@ -2970,7 +2977,7 @@ async def update_model(): ) async def add_context_length(): - await asyncio.sleep(0.15) # Simulate some async work + await asyncio.sleep(0.015) # Simulate some async work main_span.update( metadata={ "llm_config": { @@ -2982,7 +2989,7 @@ async def add_context_length(): ) async def update_user_id(): - await asyncio.sleep(0.08) # Simulate some async work + await asyncio.sleep(0.008) # Simulate some async work main_span.update( metadata={ "request_info": { @@ -3349,6 +3356,7 @@ def langfuse_client(self, monkeypatch): public_key="test-public-key", secret_key="test-secret-key", base_url="http://test-host", + tracing_enabled=False, ) return client diff --git a/tests/test_parse_usage_model.py b/tests/unit/test_parse_usage_model.py similarity index 100% rename from tests/test_parse_usage_model.py rename to tests/unit/test_parse_usage_model.py diff --git a/tests/unit/test_prompt.py b/tests/unit/test_prompt.py new file mode 100644 index 000000000..eadfb8221 --- /dev/null +++ b/tests/unit/test_prompt.py @@ -0,0 +1,750 @@ +from unittest.mock import Mock, patch + +import pytest + +from langfuse._client.client import Langfuse +from langfuse._utils.prompt_cache import ( + DEFAULT_PROMPT_CACHE_TTL_SECONDS, + PromptCache, + PromptCacheItem, + PromptCacheTaskManager, +) +from langfuse.api import NotFoundError, Prompt_Chat, Prompt_Text +from langfuse.model import ChatPromptClient, TextPromptClient + + +@pytest.mark.parametrize( + ("variables", "placeholders", "expected_len", "expected_contents"), + [ + ( + {"role": "helpful", "task": "coding"}, + {}, + 3, + ["You are a helpful assistant", None, "Help me with coding"], + ), + ( + {}, + {}, + 3, + ["You are a {{role}} assistant", None, "Help me with {{task}}"], + ), + ( + {}, + { + "examples": [ + {"role": "user", "content": "Example question"}, + {"role": "assistant", "content": "Example answer"}, + ], + }, + 4, + [ + "You are a {{role}} assistant", + "Example question", + "Example answer", + "Help me with {{task}}", + ], + ), + ( + {"role": "helpful", "task": "coding"}, + { + "examples": [ + {"role": "user", "content": "Show me {{task}}"}, + {"role": "assistant", "content": "Here's {{task}}"}, + ], + }, + 4, + [ + "You are a helpful assistant", + "Show me coding", + "Here's coding", + "Help me with coding", + ], + ), + ( + {"role": "helpful", "task": "coding"}, + {"unused": [{"role": "user", "content": "Won't appear"}]}, + 3, + ["You are a helpful assistant", None, "Help me with coding"], + ), + ( + {"role": "helpful", "task": "coding"}, + {"examples": "not a list"}, + 3, + [ + "You are a helpful assistant", + "not a list", + "Help me with coding", + ], + ), + ( + {"role": "helpful", "task": "coding"}, + { + "examples": [ + "invalid message", + {"role": "user", "content": "valid message"}, + ] + }, + 4, + [ + "You are a helpful assistant", + "['invalid message', {'role': 'user', 'content': 'valid message'}]", + "valid message", + "Help me with coding", + ], + ), + ], +) +def test_compile_with_placeholders( + variables, placeholders, expected_len, expected_contents +) -> None: + mock_prompt = Prompt_Chat( + name="test_prompt", + version=1, + type="chat", + config={}, + tags=[], + labels=[], + prompt=[ + {"role": "system", "content": "You are a {{role}} assistant"}, + {"type": "placeholder", "name": "examples"}, + {"role": "user", "content": "Help me with {{task}}"}, + ], + ) + + compile_kwargs = {**placeholders, **variables} + result = ChatPromptClient(mock_prompt).compile(**compile_kwargs) + + assert len(result) == expected_len + for i, expected_content in enumerate(expected_contents): + if expected_content is None: + assert "type" in result[i] and result[i]["type"] == "placeholder" + elif isinstance(result[i], str): + assert result[i] == expected_content + else: + assert "content" in result[i] + assert result[i]["content"] == expected_content + + +@pytest.fixture +def langfuse(): + from langfuse._client.resource_manager import LangfuseResourceManager + + langfuse_instance = Langfuse() + langfuse_instance.api = Mock() + + if langfuse_instance._resources is None: + langfuse_instance._resources = Mock(spec=LangfuseResourceManager) + langfuse_instance._resources.prompt_cache = PromptCache() + + return langfuse_instance + + +def wait_for_prompt_refresh(langfuse: Langfuse) -> None: + langfuse._resources.prompt_cache._task_manager.wait_for_idle() + + +def test_prompt_cache_task_manager_pauses_all_workers_before_broadcasting_shutdown(): + manager = PromptCacheTaskManager(threads=0) + events = [] + + class FakeConsumer: + def __init__(self, identifier): + self._identifier = identifier + + def pause(self): + events.append(("pause", self._identifier)) + + def join(self): + events.append(("join", self._identifier)) + + class FakeQueue: + def put(self, item): + events.append(("put", item)) + + manager._consumers = [FakeConsumer(0), FakeConsumer(1), FakeConsumer(2)] + manager._queue = FakeQueue() + + manager.shutdown() + + assert [event[0] for event in events] == [ + "pause", + "pause", + "pause", + "put", + "put", + "put", + "join", + "join", + "join", + ] + + +def test_get_fresh_prompt(langfuse): + prompt_name = "test_get_fresh_prompt" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + type="text", + labels=[], + config={}, + tags=[], + ) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result = langfuse.get_prompt(prompt_name, fallback="fallback") + mock_server_call.assert_called_once_with( + prompt_name, + version=None, + label=None, + request_options=None, + ) + + assert result == TextPromptClient(prompt) + + +def test_throw_if_name_unspecified(langfuse): + with pytest.raises(ValueError) as exc_info: + langfuse.get_prompt("") + + assert "Prompt name cannot be empty" in str(exc_info.value) + + +def test_throw_when_failing_fetch_and_no_cache(langfuse): + mock_server_call = langfuse.api.prompts.get + mock_server_call.side_effect = Exception("Prompt not found") + + with pytest.raises(Exception) as exc_info: + langfuse.get_prompt("failing_fetch_and_no_cache") + + assert "Prompt not found" in str(exc_info.value) + + +def test_using_custom_prompt_timeouts(langfuse): + prompt_name = "test_using_custom_prompt_timeouts" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + type="text", + labels=[], + config={}, + tags=[], + ) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result = langfuse.get_prompt( + prompt_name, fallback="fallback", fetch_timeout_seconds=1000 + ) + mock_server_call.assert_called_once_with( + prompt_name, + version=None, + label=None, + request_options={"timeout_in_seconds": 1000}, + ) + + assert result == TextPromptClient(prompt) + + +def test_throw_if_cache_ttl_seconds_positional_argument(langfuse): + with pytest.raises(TypeError) as exc_info: + langfuse.get_prompt("test ttl seconds in positional arg", 20) + + assert "positional arguments" in str(exc_info.value) + + +def test_get_valid_cached_prompt(langfuse): + prompt_name = "test_get_valid_cached_prompt" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + type="text", + labels=[], + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name, fallback="fallback") + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + result_call_2 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + +def test_get_valid_cached_chat_prompt_by_label(langfuse): + prompt_name = "test_get_valid_cached_chat_prompt_by_label" + prompt = Prompt_Chat( + name=prompt_name, + version=1, + prompt=[{"role": "system", "content": "Make me laugh"}], + labels=["test"], + type="chat", + config={}, + tags=[], + ) + prompt_client = ChatPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name, label="test") + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + result_call_2 = langfuse.get_prompt(prompt_name, label="test") + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + +def test_get_valid_cached_chat_prompt_by_version(langfuse): + prompt_name = "test_get_valid_cached_chat_prompt_by_version" + prompt = Prompt_Chat( + name=prompt_name, + version=1, + prompt=[{"role": "system", "content": "Make me laugh"}], + labels=["test"], + type="chat", + config={}, + tags=[], + ) + prompt_client = ChatPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name, version=1) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + result_call_2 = langfuse.get_prompt(prompt_name, version=1) + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + +def test_get_valid_cached_production_chat_prompt(langfuse): + prompt_name = "test_get_valid_cached_production_chat_prompt" + prompt = Prompt_Chat( + name=prompt_name, + version=1, + prompt=[{"role": "system", "content": "Make me laugh"}], + labels=["test"], + type="chat", + config={}, + tags=[], + ) + prompt_client = ChatPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + result_call_2 = langfuse.get_prompt(prompt_name, label="production") + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + +def test_get_valid_cached_chat_prompt(langfuse): + prompt_name = "test_get_valid_cached_chat_prompt" + prompt = Prompt_Chat( + name=prompt_name, + version=1, + prompt=[{"role": "system", "content": "Make me laugh"}], + labels=[], + type="chat", + config={}, + tags=[], + ) + prompt_client = ChatPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + result_call_2 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_get_fresh_prompt_when_expired_cache_custom_ttl(mock_time, langfuse: Langfuse): + mock_time.return_value = 0 + ttl_seconds = 20 + + prompt_name = "test_get_fresh_prompt_when_expired_cache_custom_ttl" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + config={"temperature": 0.9}, + labels=[], + type="text", + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=ttl_seconds) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + mock_time.return_value = ttl_seconds - 1 + + result_call_2 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + mock_time.return_value = ttl_seconds + 1 + + result_call_3 = langfuse.get_prompt(prompt_name) + + wait_for_prompt_refresh(langfuse) + + assert mock_server_call.call_count == 2 + assert result_call_3 == prompt_client + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_disable_caching_when_ttl_zero(mock_time, langfuse: Langfuse): + mock_time.return_value = 0 + prompt_name = "test_disable_caching_when_ttl_zero" + + prompt1 = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt2 = Prompt_Text( + name=prompt_name, + version=2, + prompt="Tell me a joke", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt3 = Prompt_Text( + name=prompt_name, + version=3, + prompt="Share a funny story", + labels=[], + type="text", + config={}, + tags=[], + ) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.side_effect = [prompt1, prompt2, prompt3] + + result1 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=0) + assert mock_server_call.call_count == 1 + assert result1 == TextPromptClient(prompt1) + + result2 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=0) + assert mock_server_call.call_count == 2 + assert result2 == TextPromptClient(prompt2) + + result3 = langfuse.get_prompt(prompt_name, cache_ttl_seconds=0) + assert mock_server_call.call_count == 3 + assert result3 == TextPromptClient(prompt3) + + assert result1 != result2 != result3 + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_get_stale_prompt_when_expired_cache_default_ttl(mock_time, langfuse: Langfuse): + import logging + + logging.basicConfig(level=logging.DEBUG) + mock_time.return_value = 0 + + prompt_name = "test_get_stale_prompt_when_expired_cache_default_ttl" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + updated_prompt = Prompt_Text( + name=prompt_name, + version=2, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + mock_server_call.return_value = updated_prompt + + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 + + stale_result = langfuse.get_prompt(prompt_name) + assert stale_result == prompt_client + + langfuse.get_prompt(prompt_name) + langfuse.get_prompt(prompt_name) + langfuse.get_prompt(prompt_name) + langfuse.get_prompt(prompt_name) + + wait_for_prompt_refresh(langfuse) + + assert mock_server_call.call_count == 2 + + updated_result = langfuse.get_prompt(prompt_name) + assert updated_result.version == 2 + assert updated_result == TextPromptClient(updated_prompt) + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_skip_redundant_refresh_when_cache_already_updated( + mock_time, langfuse: Langfuse +) -> None: + prompt_name = "test_skip_redundant_refresh_when_cache_already_updated" + cache_key = PromptCache.generate_cache_key(prompt_name, version=None, label=None) + + mock_time.return_value = 0 + + initial_prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + updated_prompt = Prompt_Text( + name=prompt_name, + version=2, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + + stale_result = TextPromptClient(initial_prompt) + fresh_result = TextPromptClient(updated_prompt) + + langfuse._resources.prompt_cache.set(cache_key, stale_result, None) + stale_item = langfuse._resources.prompt_cache.get(cache_key) + assert stale_item is not None + + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 + assert stale_item.is_expired() + + langfuse._resources.prompt_cache.set(cache_key, fresh_result, None) + + add_task_mock = Mock() + langfuse._resources.prompt_cache._task_manager.add_task = add_task_mock + + langfuse._resources.prompt_cache.add_refresh_prompt_task_if_current( + cache_key, + stale_item, + Mock(), + ) + + add_task_mock.assert_not_called() + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_get_fresh_prompt_when_expired_cache_default_ttl(mock_time, langfuse: Langfuse): + mock_time.return_value = 0 + + prompt_name = "test_get_fresh_prompt_when_expired_cache_default_ttl" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS - 1 + + result_call_2 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_2 == prompt_client + + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 + + result_call_3 = langfuse.get_prompt(prompt_name) + wait_for_prompt_refresh(langfuse) + + assert mock_server_call.call_count == 2 + assert result_call_3 == prompt_client + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_get_expired_prompt_when_failing_fetch(mock_time, langfuse: Langfuse): + mock_time.return_value = 0 + + prompt_name = "test_get_expired_prompt_when_failing_fetch" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + mock_time.return_value = DEFAULT_PROMPT_CACHE_TTL_SECONDS + 1 + mock_server_call.side_effect = Exception("Server error") + + result_call_2 = langfuse.get_prompt(prompt_name, max_retries=1) + wait_for_prompt_refresh(langfuse) + + assert mock_server_call.call_count == 3 + assert result_call_2 == prompt_client + + +@patch.object(PromptCacheItem, "get_epoch_seconds") +def test_evict_prompt_cache_entry_when_refresh_returns_not_found( + mock_time, langfuse: Langfuse +) -> None: + mock_time.return_value = 0 + + prompt_name = "test_evict_prompt_cache_entry_when_refresh_returns_not_found" + ttl_seconds = 5 + fallback_prompt = "fallback text prompt" + + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + cache_key = PromptCache.generate_cache_key(prompt_name, version=None, label=None) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + initial_result = langfuse.get_prompt( + prompt_name, + cache_ttl_seconds=ttl_seconds, + max_retries=0, + ) + assert initial_result == prompt_client + assert langfuse._resources.prompt_cache.get(cache_key) is not None + + mock_time.return_value = ttl_seconds + 1 + + def raise_not_found(*_args: object, **_kwargs: object) -> None: + raise NotFoundError({"message": "Prompt not found"}) + + mock_server_call.side_effect = raise_not_found + + stale_result = langfuse.get_prompt( + prompt_name, + cache_ttl_seconds=ttl_seconds, + max_retries=0, + ) + assert stale_result == prompt_client + + wait_for_prompt_refresh(langfuse) + + assert langfuse._resources.prompt_cache.get(cache_key) is None + + fallback_result = langfuse.get_prompt( + prompt_name, + cache_ttl_seconds=ttl_seconds, + fallback=fallback_prompt, + max_retries=0, + ) + assert fallback_result.is_fallback + assert fallback_result.prompt == fallback_prompt + + +def test_get_fresh_prompt_when_version_changes(langfuse: Langfuse): + prompt_name = "test_get_fresh_prompt_when_version_changes" + prompt = Prompt_Text( + name=prompt_name, + version=1, + prompt="Make me laugh", + labels=[], + type="text", + config={}, + tags=[], + ) + prompt_client = TextPromptClient(prompt) + + mock_server_call = langfuse.api.prompts.get + mock_server_call.return_value = prompt + + result_call_1 = langfuse.get_prompt(prompt_name, version=1) + assert mock_server_call.call_count == 1 + assert result_call_1 == prompt_client + + version_changed_prompt = Prompt_Text( + name=prompt_name, + version=2, + labels=[], + prompt="Make me laugh", + type="text", + config={}, + tags=[], + ) + version_changed_prompt_client = TextPromptClient(version_changed_prompt) + mock_server_call.return_value = version_changed_prompt + + result_call_2 = langfuse.get_prompt(prompt_name, version=2) + assert mock_server_call.call_count == 2 + assert result_call_2 == version_changed_prompt_client diff --git a/tests/test_prompt_atexit.py b/tests/unit/test_prompt_atexit.py similarity index 93% rename from tests/test_prompt_atexit.py rename to tests/unit/test_prompt_atexit.py index 2eac27ceb..ccd1b0f19 100644 --- a/tests/test_prompt_atexit.py +++ b/tests/unit/test_prompt_atexit.py @@ -26,7 +26,9 @@ def wait_2_sec(): # 8 times for i in range(8): - prompt_cache.add_refresh_prompt_task(f"key_wait_2_sec_i_{i}", lambda: wait_2_sec()) + prompt_cache.add_refresh_prompt_task( + f"key_wait_2_sec_i_{i}", lambda: wait_2_sec() + ) """ process = subprocess.Popen( @@ -79,7 +81,9 @@ def wait_2_sec(): time.sleep(2) async def add_new_prompt_refresh(i: int): - prompt_cache.add_refresh_prompt_task(f"key_wait_2_sec_i_{i}", lambda: wait_2_sec()) + prompt_cache.add_refresh_prompt_task( + f"key_wait_2_sec_i_{i}", lambda: wait_2_sec() + ) # 8 times tasks = [add_new_prompt_refresh(i) for i in range(8)] diff --git a/tests/test_prompt_compilation.py b/tests/unit/test_prompt_compilation.py similarity index 100% rename from tests/test_prompt_compilation.py rename to tests/unit/test_prompt_compilation.py diff --git a/tests/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py similarity index 99% rename from tests/test_propagate_attributes.py rename to tests/unit/test_propagate_attributes.py index d771a3d74..67cd703c3 100644 --- a/tests/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -6,7 +6,6 @@ """ import concurrent.futures -import time from datetime import datetime import pytest @@ -17,7 +16,7 @@ from langfuse._client.constants import LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT from langfuse._client.datasets import DatasetClient from langfuse.api import Dataset, DatasetItem, DatasetStatus -from tests.test_otel import TestOTelBase +from tests.unit.test_otel import TestOTelBase class TestPropagateAttributesBase(TestOTelBase): @@ -1460,7 +1459,7 @@ async def create_trace_with_user(user_id: str): """Create a trace with specific user_id.""" with langfuse_client.start_as_current_observation(name=f"trace-{user_id}"): with propagate_attributes(user_id=user_id): - await asyncio.sleep(0.01) # Simulate async work + await asyncio.sleep(0.001) # Simulate async work span = langfuse_client.start_observation(name=f"span-{user_id}") span.end() @@ -2305,7 +2304,7 @@ async def test_experiment_propagates_user_id_in_async_context( local_data = [{"input": "test input", "expected_output": "expected output"}] async def async_task(*, item, **kwargs): - await asyncio.sleep(0.01) + await asyncio.sleep(0.001) return f"processed: {item['input']}" with propagate_attributes(user_id="async-experiment-user"): @@ -2316,7 +2315,6 @@ async def async_task(*, item, **kwargs): ) langfuse_client.flush() - time.sleep(0.1) root_span = self.get_span_by_name(memory_exporter, "experiment-item-run") self.verify_span_attribute( @@ -2361,7 +2359,6 @@ def task_with_child_spans(*, item, **kwargs): # Flush to ensure spans are exported langfuse_client.flush() - time.sleep(0.1) # Get the root span root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") @@ -2495,7 +2492,6 @@ def test_experiment_id_is_stable_across_local_items( ) langfuse_client.flush() - time.sleep(0.1) root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") experiment_ids = { @@ -2587,7 +2583,6 @@ def task_with_children(*, item, **kwargs): ) langfuse_client.flush() - time.sleep(0.1) # Verify root has dataset-specific attributes root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") @@ -2721,7 +2716,6 @@ def task_with_nested_spans(*, item, **kwargs): ) langfuse_client.flush() - time.sleep(0.1) root_spans = self.get_spans_by_name(memory_exporter, "experiment-item-run") first_root = root_spans[0] @@ -2779,8 +2773,6 @@ def task_with_nested_spans(*, item, **kwargs): def test_experiment_metadata_merging(self, langfuse_client, memory_exporter): """Test that experiment metadata and item metadata are both propagated correctly.""" - import time - from langfuse._client.attributes import _serialize # Rich metadata @@ -2817,7 +2809,6 @@ def task_with_child(*, item, **kwargs): ) langfuse_client.flush() - time.sleep(0.1) # Verify root span has environment set root_span = self.get_span_by_name(memory_exporter, "experiment-item-run") diff --git a/tests/test_resource_manager.py b/tests/unit/test_resource_manager.py similarity index 64% rename from tests/test_resource_manager.py rename to tests/unit/test_resource_manager.py index cce6446e9..d0880dcd6 100644 --- a/tests/test_resource_manager.py +++ b/tests/unit/test_resource_manager.py @@ -1,6 +1,9 @@ """Test the LangfuseResourceManager and get_client() function.""" +from queue import Queue +from types import SimpleNamespace from typing import Sequence +from unittest.mock import Mock from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult @@ -8,6 +11,9 @@ from langfuse import Langfuse from langfuse._client.get_client import get_client from langfuse._client.resource_manager import LangfuseResourceManager +from langfuse._task_manager.media_manager import MediaManager +from langfuse._task_manager.media_upload_consumer import MediaUploadConsumer +from langfuse._task_manager.score_ingestion_consumer import ScoreIngestionConsumer class NoOpSpanExporter(SpanExporter): @@ -20,11 +26,15 @@ def shutdown(self) -> None: pass -def test_get_client_preserves_all_settings(): +def test_get_client_preserves_all_settings(monkeypatch): """Test that get_client() preserves environment and all client settings.""" with LangfuseResourceManager._lock: LangfuseResourceManager._instances.clear() + monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-comprehensive-default") + monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-comprehensive-default") + monkeypatch.setenv("LANGFUSE_BASE_URL", "http://localhost:3000") + def should_export(span): return span.name != "drop" @@ -122,3 +132,69 @@ def should_export_b(span): client_a.shutdown() client_b.shutdown() + + +def test_score_ingestion_consumer_pause_wakes_blocked_thread(): + consumer = ScoreIngestionConsumer( + ingestion_queue=Queue(), + identifier=0, + client=Mock(), + public_key="pk-test", + flush_interval=30, + ) + + consumer.start() + consumer.pause() + consumer.join(timeout=0.5) + + assert not consumer.is_alive() + + +def test_media_upload_consumer_signal_shutdown_wakes_blocked_thread(): + media_manager = MediaManager( + api_client=Mock(), + httpx_client=Mock(), + media_upload_queue=Queue(), + ) + consumer = MediaUploadConsumer(identifier=0, media_manager=media_manager) + + consumer.start() + consumer.pause() + media_manager.signal_shutdown() + consumer.join(timeout=0.5) + + assert not consumer.is_alive() + + +def test_stop_and_join_consumer_threads_broadcasts_media_shutdown_after_pausing_all(): + events = [] + + class FakeConsumer: + def __init__(self, identifier): + self._identifier = identifier + + def pause(self): + events.append(("pause", self._identifier)) + + def join(self): + events.append(("join", self._identifier)) + + class FakeMediaManager: + def signal_shutdown(self, *, count): + events.append(("signal_shutdown", count)) + + fake_resource_manager = SimpleNamespace( + _media_upload_consumers=[FakeConsumer(0), FakeConsumer(1)], + _ingestion_consumers=[], + _media_manager=FakeMediaManager(), + ) + + LangfuseResourceManager._stop_and_join_consumer_threads(fake_resource_manager) + + assert events == [ + ("pause", 0), + ("pause", 1), + ("signal_shutdown", 2), + ("join", 0), + ("join", 1), + ] diff --git a/tests/test_serializer.py b/tests/unit/test_serializer.py similarity index 100% rename from tests/test_serializer.py rename to tests/unit/test_serializer.py diff --git a/tests/test_span_filter.py b/tests/unit/test_span_filter.py similarity index 100% rename from tests/test_span_filter.py rename to tests/unit/test_span_filter.py diff --git a/tests/test_utils.py b/tests/unit/test_utils.py similarity index 100% rename from tests/test_utils.py rename to tests/unit/test_utils.py diff --git a/tests/test_version.py b/tests/unit/test_version.py similarity index 100% rename from tests/test_version.py rename to tests/unit/test_version.py diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 7d6530b45..000000000 --- a/tests/utils.py +++ /dev/null @@ -1,25 +0,0 @@ -import base64 -import os -from time import sleep -from uuid import uuid4 - -from langfuse.api import LangfuseAPI - - -def create_uuid(): - return str(uuid4()) - - -def get_api(): - sleep(2) - - return LangfuseAPI( - username=os.environ.get("LANGFUSE_PUBLIC_KEY"), - password=os.environ.get("LANGFUSE_SECRET_KEY"), - base_url=os.environ.get("LANGFUSE_BASE_URL"), - ) - - -def encode_file_to_base64(image_path) -> str: - with open(image_path, "rb") as file: - return base64.b64encode(file.read()).decode("utf-8") From cfbe7a33cf64bc16115666d5615a4eaf717fc0b0 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Fri, 10 Apr 2026 16:48:05 +0200 Subject: [PATCH 249/296] ci: add 7-day dependabot cooldown (#1619) chore(dependabot): add 7-day cooldown --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 61538b021..e312aaf57 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,8 @@ updates: directory: "/" # Location of package manifests schedule: interval: "daily" + cooldown: + default-days: 7 rebase-strategy: "disabled" # use dependabot-rebase-stale commit-message: prefix: chore @@ -26,6 +28,8 @@ updates: directory: "/" schedule: interval: "daily" + cooldown: + default-days: 7 rebase-strategy: "disabled" commit-message: prefix: chore From ffaaaa3583b0c3d010994230eb1d2e581a199e49 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 15 Apr 2026 10:07:46 +0200 Subject: [PATCH 250/296] ci: harden GitHub Actions workflows with zizmor (#1623) * ci: harden GitHub Actions workflows with zizmor - Add `permissions: {}` defaults to ci, dependabot-rebase-stale, package-availability-check workflows - Add `persist-credentials: false` to all checkout steps - Move `${{ }}` interpolations in run blocks to env vars (release.yml) - Replace `softprops/action-gh-release` with `gh release create` - Switch claude-review from `pull_request_target` to `pull_request` with fork check - Replace spoofable `github.actor` check with `user.id` for dependabot - Add zizmor CI workflow for ongoing monitoring - Add `lookup-only: true` to mypy cache (type-checking job) - Disable uv cache in release workflow (publishes to PyPI) Co-Authored-By: Claude Opus 4.6 (1M context) * ci: fix version comment inconsistencies - zizmor.yml: align checkout comment to `# v6` matching other workflows - ci.yml: fix actions/cache comment from `# v5` to `# v5.0.4` (actual tag) Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 20 +++-- .../claude-review-maintainer-prs.yml | 5 +- .github/workflows/codeql.yml | 2 + .github/workflows/dependabot-merge.yml | 2 +- .github/workflows/dependabot-rebase-stale.yml | 2 + .../workflows/package-availability-check.yml | 2 + .github/workflows/release.yml | 78 ++++++++++++------- .github/workflows/zizmor.yml | 32 ++++++++ 8 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdac4226a..93cf05329 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,8 @@ on: branches: - "*" +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -19,12 +21,14 @@ jobs: runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv and set Python version uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.13" - enable-cache: true + enable-cache: true # zizmor: ignore[cache-poisoning] CI-only, no artifacts published - name: Install dependencies run: uv sync --locked - name: Run Ruff @@ -34,13 +38,15 @@ jobs: runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv and set Python version uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.13" - enable-cache: true - - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + enable-cache: true # zizmor: ignore[cache-poisoning] CI-only, no artifacts published + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 # zizmor: ignore[cache-poisoning] name: Cache mypy cache with: path: ./.mypy_cache @@ -73,12 +79,14 @@ jobs: name: Unit tests on Python ${{ matrix.python-version }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv and set Python version uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: ${{ matrix.python-version }} - enable-cache: true + enable-cache: true # zizmor: ignore[cache-poisoning] CI-only, no artifacts published - name: Check Python version run: python --version @@ -134,12 +142,14 @@ jobs: name: ${{ matrix.job_name }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv and set Python version uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.13" - enable-cache: true + enable-cache: true # zizmor: ignore[cache-poisoning] CI-only, no artifacts published - name: Install the project dependencies run: uv sync --locked - name: Check uv Python version diff --git a/.github/workflows/claude-review-maintainer-prs.yml b/.github/workflows/claude-review-maintainer-prs.yml index 1e1034106..3aa865bdd 100644 --- a/.github/workflows/claude-review-maintainer-prs.yml +++ b/.github/workflows/claude-review-maintainer-prs.yml @@ -1,14 +1,15 @@ name: Claude Review on Maintainer PRs on: - pull_request_target: + pull_request: types: - opened - ready_for_review jobs: comment: - if: github.event.pull_request.draft == false + # Only run on PRs that are not drafts and are from the same repository (i.e., not from forks) + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: issues: write diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 19d682989..1834f6f10 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -56,6 +56,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 1a86ae8f8..aa321ee4d 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -11,7 +11,7 @@ permissions: jobs: dependabot: runs-on: ubuntu-latest - if: ${{ github.actor == 'dependabot[bot]' }} + if: github.event.pull_request.user.id == 49699333 # dependabot[bot] steps: - name: Dependabot metadata id: metadata diff --git a/.github/workflows/dependabot-rebase-stale.yml b/.github/workflows/dependabot-rebase-stale.yml index dcbe57211..534506864 100644 --- a/.github/workflows/dependabot-rebase-stale.yml +++ b/.github/workflows/dependabot-rebase-stale.yml @@ -6,6 +6,8 @@ on: - main workflow_dispatch: +permissions: {} + jobs: rebase-dependabot: runs-on: ubuntu-latest diff --git a/.github/workflows/package-availability-check.yml b/.github/workflows/package-availability-check.yml index e8074a9ba..3f43ddd21 100644 --- a/.github/workflows/package-availability-check.yml +++ b/.github/workflows/package-availability-check.yml @@ -5,6 +5,8 @@ on: - cron: "*/30 * * * *" workflow_dispatch: +permissions: {} + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e24bd45a0..ced019620 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,32 +47,35 @@ jobs: steps: - name: Verify branch run: | - if [ "${{ github.ref }}" != "refs/heads/main" ]; then + if [ "${GITHUB_REF}" != "refs/heads/main" ]; then echo "❌ Error: Releases can only be triggered from main branch" - echo "Current ref: ${{ github.ref }}" + echo "Current ref: ${GITHUB_REF}" exit 1 fi - name: Confirm major release if: ${{ inputs.version == 'major' || inputs.version == 'premajor' }} run: | - if [ "${{ inputs.confirm_major }}" != "RELEASE MAJOR" ]; then + if [ "${INPUTS_CONFIRM_MAJOR}" != "RELEASE MAJOR" ]; then echo "❌ For major/premajor releases, set confirm_major to RELEASE MAJOR" exit 1 fi + env: + INPUTS_CONFIRM_MAJOR: ${{ inputs.confirm_major }} - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 token: ${{ secrets.GH_ACCESS_TOKEN }} + persist-credentials: false - name: Install uv and set Python version uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 with: version: "0.11.2" python-version: "3.12" - enable-cache: true + enable-cache: false - name: Configure Git env: @@ -94,10 +97,10 @@ jobs: - name: Calculate new version id: new-version run: | - current_version="${{ steps.current-version.outputs.version }}" - version_type="${{ inputs.version }}" - prerelease_type="${{ inputs.prerelease_type }}" - prerelease_increment="${{ inputs.prerelease_increment }}" + current_version="${STEPS_CURRENT_VERSION_OUTPUTS_VERSION}" + version_type="${INPUTS_VERSION}" + prerelease_type="${INPUTS_PRERELEASE_TYPE}" + prerelease_increment="${INPUTS_PRERELEASE_INCREMENT}" # Extract base version (strip any pre-release suffix like a1, b2, rc1) base_version=$(echo "$current_version" | sed -E 's/(a|b|rc)[0-9]+$//') @@ -195,18 +198,27 @@ jobs: echo "version=$new_version" >> $GITHUB_OUTPUT echo "is_prerelease=$is_prerelease" >> $GITHUB_OUTPUT echo "New version: $new_version (prerelease: $is_prerelease)" + env: + STEPS_CURRENT_VERSION_OUTPUTS_VERSION: ${{ steps.current-version.outputs.version }} + INPUTS_VERSION: ${{ inputs.version }} + INPUTS_PRERELEASE_TYPE: ${{ inputs.prerelease_type }} + INPUTS_PRERELEASE_INCREMENT: ${{ inputs.prerelease_increment }} - name: Check if tag already exists run: | - if git rev-parse "v${{ steps.new-version.outputs.version }}" >/dev/null 2>&1; then - echo "❌ Error: Tag v${{ steps.new-version.outputs.version }} already exists" + if git rev-parse "v${STEPS_NEW_VERSION_OUTPUTS_VERSION}" >/dev/null 2>&1; then + echo "❌ Error: Tag v${STEPS_NEW_VERSION_OUTPUTS_VERSION} already exists" exit 1 fi - echo "✅ Tag v${{ steps.new-version.outputs.version }} does not exist" + echo "✅ Tag v${STEPS_NEW_VERSION_OUTPUTS_VERSION} does not exist" + env: + STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} - name: Update version in pyproject.toml run: | - uv version ${{ steps.new-version.outputs.version }} + uv version ${STEPS_NEW_VERSION_OUTPUTS_VERSION} + env: + STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} - name: Verify version consistency run: | @@ -214,12 +226,14 @@ jobs: echo "pyproject.toml version: $pyproject_version" - if [ "$pyproject_version" != "${{ steps.new-version.outputs.version }}" ]; then + if [ "$pyproject_version" != "${STEPS_NEW_VERSION_OUTPUTS_VERSION}" ]; then echo "❌ Error: Version in files doesn't match expected version" exit 1 fi echo "✅ Versions are consistent: $pyproject_version" + env: + STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} - name: Build package run: uv build --no-sources @@ -250,7 +264,7 @@ jobs: ls -lh dist/ # Verify the version in the built artifacts matches - expected_version="${{ steps.new-version.outputs.version }}" + expected_version="${STEPS_NEW_VERSION_OUTPUTS_VERSION}" wheel_file=$(ls dist/*.whl | head -1) if ! echo "$wheel_file" | grep -q "$expected_version"; then echo "❌ Error: Wheel filename doesn't contain expected version $expected_version" @@ -258,6 +272,8 @@ jobs: exit 1 fi echo "✅ Artifact version verified" + env: + STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} - name: Smoke test wheel run: | @@ -270,32 +286,38 @@ jobs: - name: Commit version changes run: | git add pyproject.toml uv.lock - git commit -m "chore: release v${{ steps.new-version.outputs.version }}" + git commit -m "chore: release v${STEPS_NEW_VERSION_OUTPUTS_VERSION}" + env: + STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} - name: Create and push tag id: push-tag run: | - git tag "v${{ steps.new-version.outputs.version }}" + git tag "v${STEPS_NEW_VERSION_OUTPUTS_VERSION}" git push origin main - git push origin "v${{ steps.new-version.outputs.version }}" + git push origin "v${STEPS_NEW_VERSION_OUTPUTS_VERSION}" + env: + STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} - name: Publish to PyPI id: publish-pypi run: uv publish --trusted-publishing always - name: Create GitHub Release - id: create-release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 - with: - tag_name: v${{ steps.new-version.outputs.version }} - name: v${{ steps.new-version.outputs.version }} - generate_release_notes: true - prerelease: ${{ steps.new-version.outputs.is_prerelease == 'true' }} - files: | - dist/*.whl - dist/*.tar.gz + run: | + prerelease_flag="" + if [ "${IS_PRERELEASE}" = "true" ]; then + prerelease_flag="--prerelease" + fi + gh release create "v${VERSION}" \ + --title "v${VERSION}" \ + --generate-notes \ + $prerelease_flag \ + dist/*.whl dist/*.tar.gz env: - GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} + GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} + VERSION: ${{ steps.new-version.outputs.version }} + IS_PRERELEASE: ${{ steps.new-version.outputs.is_prerelease }} - name: Notify Slack on success if: success() diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000..c706f5c43 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,32 @@ +--- +name: Check GitHub Actions + +on: + workflow_dispatch: + push: + branches: + - "main" + merge_group: + pull_request: + branches: + - "main" + +permissions: {} + +jobs: + zizmor: + name: Check GitHub Actions security + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + with: + advanced-security: true From c445f3db107655c566915c630229077f8f62582b Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 15 Apr 2026 08:08:43 +0000 Subject: [PATCH 251/296] chore: release v4.3.0rc1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 68638167c..fe6e1fa34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.2.0" +version = "4.3.0rc1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index a74858998..431dc847f 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-03T11:55:15.924547735Z" +exclude-newer = "2026-04-08T08:08:39.469707872Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.2.0" +version = "4.3.0rc1" source = { editable = "." } dependencies = [ { name = "backoff" }, From be74cbc938174735596e2cbb7ba420cc4bd62ae7 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 15 Apr 2026 10:22:44 +0200 Subject: [PATCH 252/296] ci: make uv action tag explicit (#1625) --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93cf05329..9a460a741 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: version: "0.11.2" python-version: "3.13" @@ -41,7 +41,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: version: "0.11.2" python-version: "3.13" @@ -82,7 +82,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: version: "0.11.2" python-version: ${{ matrix.python-version }} @@ -145,7 +145,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: version: "0.11.2" python-version: "3.13" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ced019620..bc81ae2a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,7 @@ jobs: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: version: "0.11.2" python-version: "3.12" From 1cbdc9355baff347b657ef222bce55b1d339640c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:11:11 +0200 Subject: [PATCH 253/296] fix(langchain): propagate trace name metadata (#1626) --- langfuse/langchain/CallbackHandler.py | 7 +++ tests/unit/test_langchain.py | 83 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 8d2c8db90..80b7114e5 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -287,6 +287,11 @@ def _parse_langfuse_trace_attributes( ): attributes["user_id"] = metadata["langfuse_user_id"] + if "langfuse_trace_name" in metadata and isinstance( + metadata["langfuse_trace_name"], str + ): + attributes["trace_name"] = metadata["langfuse_trace_name"] + if tags is not None or ( "langfuse_tags" in metadata and isinstance(metadata["langfuse_tags"], list) ): @@ -369,6 +374,7 @@ def on_chain_start( session_id=parsed_trace_attributes.get("session_id", None), tags=parsed_trace_attributes.get("tags", None), metadata=parsed_trace_attributes.get("metadata", None), + trace_name=parsed_trace_attributes.get("trace_name", None), ) self._propagation_context_manager.__enter__() @@ -1403,6 +1409,7 @@ def _strip_langfuse_keys_from_dict( "langfuse_session_id", "langfuse_user_id", "langfuse_tags", + "langfuse_trace_name", ] metadata_copy = metadata.copy() diff --git a/tests/unit/test_langchain.py b/tests/unit/test_langchain.py index b4c1ba2ee..5d8406e9c 100644 --- a/tests/unit/test_langchain.py +++ b/tests/unit/test_langchain.py @@ -1,4 +1,6 @@ +from contextvars import copy_context from unittest.mock import patch +from uuid import uuid4 import pytest from langchain.messages import HumanMessage @@ -166,3 +168,84 @@ def test_chat_model_error_marks_generation_error(langfuse_memory_client, get_spa assert ( "boom" in span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] ) + + +def test_root_chain_metadata_propagates_trace_name( + langfuse_memory_client, get_span, find_spans +): + response = ChatResult( + generations=[ + ChatGeneration( + message=AIMessage(content="knock knock"), + text="knock knock", + ) + ], + llm_output={ + "token_usage": { + "prompt_tokens": 4, + "completion_tokens": 2, + "total_tokens": 6, + }, + "model_name": "gpt-4o-mini", + }, + ) + + with patch.object(ChatOpenAI, "_generate", return_value=response): + handler = CallbackHandler() + prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}") + chain = prompt | ChatOpenAI(api_key="test", temperature=0) | StrOutputParser() + + result = chain.invoke( + {"topic": "otters"}, + config={ + "callbacks": [handler], + "metadata": {"langfuse_trace_name": "langchain-trace-name"}, + }, + ) + + assert result == "knock knock" + + langfuse_memory_client.flush() + root_span = get_span("RunnableSequence") + generation_span = get_span("ChatOpenAI") + + assert ( + root_span.attributes[LangfuseOtelSpanAttributes.TRACE_NAME] + == "langchain-trace-name" + ) + assert ( + generation_span.attributes[LangfuseOtelSpanAttributes.TRACE_NAME] + == "langchain-trace-name" + ) + assert ( + f"{LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langfuse_trace_name" + not in root_span.attributes + ) + assert len(find_spans("ChatOpenAI")) == 1 + + +def test_root_chain_exports_when_end_runs_in_copied_context( + langfuse_memory_client, get_span +): + handler = CallbackHandler() + run_id = uuid4() + + handler.on_chain_start( + {"id": ["RunnableSequence"]}, + {"topic": "otters"}, + run_id=run_id, + metadata={"langfuse_trace_name": "async-root-trace"}, + ) + + copy_context().run( + handler.on_chain_end, + {"output": "knock knock"}, + run_id=run_id, + ) + + langfuse_memory_client.flush() + root_span = get_span("RunnableSequence") + + assert root_span.attributes[LangfuseOtelSpanAttributes.TRACE_NAME] == ( + "async-root-trace" + ) From 3a65ce851bdba6768803c71d48b759310f29bebf Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 16 Apr 2026 12:20:14 +0000 Subject: [PATCH 254/296] chore: release v4.3.1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fe6e1fa34..4914598f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.3.0rc1" +version = "4.3.1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 431dc847f..4050037f7 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-08T08:08:39.469707872Z" +exclude-newer = "2026-04-09T12:20:09.155957247Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.3.0rc1" +version = "4.3.1" source = { editable = "." } dependencies = [ { name = "backoff" }, From ee27fbced19128ad23c960b63b37af16072b8dc9 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Fri, 17 Apr 2026 13:59:26 +0200 Subject: [PATCH 255/296] fix(ci): disable zizmor advanced security to unblock releases (#1630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): disable zizmor advanced security to unblock release pushes With advanced-security enabled, zizmor uploads SARIF to GitHub Code Scanning. The branch protection ruleset then requires those results before allowing pushes to main. This blocks the release workflow because its version-bump commit doesn't exist on GitHub yet, so code scanning can't produce results for it — a chicken-and-egg problem. Switching to advanced-security: false keeps zizmor as a regular CI check (pass/fail) without uploading to Code Scanning, avoiding the branch protection conflict. Also sets min-severity to medium to filter out noisy low-severity findings. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(ci): add comment explaining advanced-security: false Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/zizmor.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index c706f5c43..725619c3f 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -19,7 +19,6 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: - security-events: write contents: read steps: - name: Checkout @@ -29,4 +28,7 @@ jobs: - name: Run zizmor uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 with: - advanced-security: true + # Using false as a code scanning ruleset would block the release + # workflow which creates a new commit and pushes directly to main. + advanced-security: false + min-severity: medium From 264b94d3aec58f971c827c3b6a44f6d35f41624b Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 17 Apr 2026 12:22:38 +0000 Subject: [PATCH 256/296] chore: release v4.4.0b1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4914598f6..dc892fb52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.3.1" +version = "4.4.0b1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 4050037f7..97b88a571 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-09T12:20:09.155957247Z" +exclude-newer = "2026-04-10T12:22:34.304101501Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.3.1" +version = "4.4.0b1" source = { editable = "." } dependencies = [ { name = "backoff" }, From 26ab5d9a290fe41377f1dfefbc4e7ef029cccfa4 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:52:11 +0200 Subject: [PATCH 257/296] fix(observe): preserve streaming context without output capture (#1628) --- langfuse/_client/observe.py | 185 +++++++++++++++++++----------------- tests/unit/test_observe.py | 92 ++++++++++++++++++ 2 files changed, 191 insertions(+), 86 deletions(-) create mode 100644 tests/unit/test_observe.py diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index c648a0a62..3ada60bc9 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -290,42 +290,15 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any: try: result = await func(*args, **kwargs) - - if capture_output is True: - if inspect.isgenerator(result): - is_return_type_generator = True - - return self._wrap_sync_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) - - if inspect.isasyncgen(result): - is_return_type_generator = True - - return self._wrap_async_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) - - # handle starlette.StreamingResponse - if type(result).__name__ == "StreamingResponse" and hasattr( - result, "body_iterator" - ): - is_return_type_generator = True - - result.body_iterator = ( - self._wrap_async_generator_result( - langfuse_span_or_generation, - result.body_iterator, - transform_to_string, - ) - ) - - langfuse_span_or_generation.update(output=result) - + ( + is_return_type_generator, + result, + ) = self._handle_observe_result( + langfuse_span_or_generation, + result, + capture_output=capture_output, + transform_to_string=transform_to_string, + ) return result except (Exception, asyncio.CancelledError) as e: langfuse_span_or_generation.update( @@ -408,42 +381,15 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any: try: result = func(*args, **kwargs) - - if capture_output is True: - if inspect.isgenerator(result): - is_return_type_generator = True - - return self._wrap_sync_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) - - if inspect.isasyncgen(result): - is_return_type_generator = True - - return self._wrap_async_generator_result( - langfuse_span_or_generation, - result, - transform_to_string, - ) - - # handle starlette.StreamingResponse - if type(result).__name__ == "StreamingResponse" and hasattr( - result, "body_iterator" - ): - is_return_type_generator = True - - result.body_iterator = ( - self._wrap_async_generator_result( - langfuse_span_or_generation, - result.body_iterator, - transform_to_string, - ) - ) - - langfuse_span_or_generation.update(output=result) - + ( + is_return_type_generator, + result, + ) = self._handle_observe_result( + langfuse_span_or_generation, + result, + capture_output=capture_output, + transform_to_string=transform_to_string, + ) return result except (Exception, asyncio.CancelledError) as e: langfuse_span_or_generation.update( @@ -493,6 +439,7 @@ def _wrap_sync_generator_result( LangfuseGuardrail, ], generator: Generator, + capture_output: bool, transform_to_string: Optional[Callable[[Iterable], str]] = None, ) -> Any: preserved_context = contextvars.copy_context() @@ -501,6 +448,7 @@ def _wrap_sync_generator_result( generator, preserved_context, langfuse_span_or_generation, + capture_output, transform_to_string, ) @@ -518,6 +466,7 @@ def _wrap_async_generator_result( LangfuseGuardrail, ], generator: AsyncGenerator, + capture_output: bool, transform_to_string: Optional[Callable[[Iterable], str]] = None, ) -> Any: preserved_context = contextvars.copy_context() @@ -526,9 +475,61 @@ def _wrap_async_generator_result( generator, preserved_context, langfuse_span_or_generation, + capture_output, transform_to_string, ) + def _handle_observe_result( + self, + langfuse_span_or_generation: Union[ + LangfuseSpan, + LangfuseGeneration, + LangfuseAgent, + LangfuseTool, + LangfuseChain, + LangfuseRetriever, + LangfuseEvaluator, + LangfuseEmbedding, + LangfuseGuardrail, + ], + result: Any, + *, + capture_output: bool, + transform_to_string: Optional[Callable[[Iterable], str]] = None, + ) -> Tuple[bool, Any]: + if inspect.isgenerator(result): + return True, self._wrap_sync_generator_result( + langfuse_span_or_generation, + result, + capture_output, + transform_to_string, + ) + + if inspect.isasyncgen(result): + return True, self._wrap_async_generator_result( + langfuse_span_or_generation, + result, + capture_output, + transform_to_string, + ) + + # handle starlette.StreamingResponse + if type(result).__name__ == "StreamingResponse" and hasattr( + result, "body_iterator" + ): + result.body_iterator = self._wrap_async_generator_result( + langfuse_span_or_generation, + result.body_iterator, + capture_output, + transform_to_string, + ) + return True, result + + if capture_output is True: + langfuse_span_or_generation.update(output=result) + + return False, result + _decorator = LangfuseDecorator() @@ -553,12 +554,14 @@ def __init__( LangfuseEmbedding, LangfuseGuardrail, ], + capture_output: bool, transform_fn: Optional[Callable[[Iterable], str]], ) -> None: self.generator = generator self.context = context self.items: List[Any] = [] self.span = span + self.capture_output = capture_output self.transform_fn = transform_fn def __iter__(self) -> "_ContextPreservedSyncGeneratorWrapper": @@ -568,21 +571,25 @@ def __next__(self) -> Any: try: # Run the generator's __next__ in the preserved context item = self.context.run(next, self.generator) - self.items.append(item) + if self.capture_output: + self.items.append(item) return item except StopIteration: # Handle output and span cleanup when generator is exhausted - output: Any = self.items + if self.capture_output: + output: Any = self.items + + if self.transform_fn is not None: + output = self.transform_fn(self.items) - if self.transform_fn is not None: - output = self.transform_fn(self.items) + elif all(isinstance(item, str) for item in self.items): + output = "".join(self.items) - elif all(isinstance(item, str) for item in self.items): - output = "".join(self.items) + self.span.update(output=output) - self.span.update(output=output).end() + self.span.end() raise # Re-raise StopIteration @@ -612,12 +619,14 @@ def __init__( LangfuseEmbedding, LangfuseGuardrail, ], + capture_output: bool, transform_fn: Optional[Callable[[Iterable], str]], ) -> None: self.generator = generator self.context = context self.items: List[Any] = [] self.span = span + self.capture_output = capture_output self.transform_fn = transform_fn def __aiter__(self) -> "_ContextPreservedAsyncGeneratorWrapper": @@ -636,21 +645,25 @@ async def __anext__(self) -> Any: # Python < 3.10 fallback - context parameter not supported item = await self.generator.__anext__() - self.items.append(item) + if self.capture_output: + self.items.append(item) return item except StopAsyncIteration: # Handle output and span cleanup when generator is exhausted - output: Any = self.items + if self.capture_output: + output: Any = self.items + + if self.transform_fn is not None: + output = self.transform_fn(self.items) - if self.transform_fn is not None: - output = self.transform_fn(self.items) + elif all(isinstance(item, str) for item in self.items): + output = "".join(self.items) - elif all(isinstance(item, str) for item in self.items): - output = "".join(self.items) + self.span.update(output=output) - self.span.update(output=output).end() + self.span.end() raise # Re-raise StopAsyncIteration except (Exception, asyncio.CancelledError) as e: diff --git a/tests/unit/test_observe.py b/tests/unit/test_observe.py new file mode 100644 index 000000000..94a2cbb83 --- /dev/null +++ b/tests/unit/test_observe.py @@ -0,0 +1,92 @@ +import sys + +import pytest + +from langfuse import observe +from langfuse._client.attributes import LangfuseOtelSpanAttributes + + +def _finished_spans_by_name(memory_exporter, name: str): + return [span for span in memory_exporter.get_finished_spans() if span.name == name] + + +def test_sync_generator_preserves_context_without_output_capture( + langfuse_memory_client, memory_exporter +): + @observe(name="child_step") + def child_step(index: int) -> str: + return f"item_{index}" + + @observe(name="root", capture_output=False) + def root(): + def body(): + for index in range(2): + yield child_step(index) + + return body() + + generator = root() + + assert memory_exporter.get_finished_spans() == [] + + assert list(generator) == ["item_0", "item_1"] + assert generator.items == [] + + langfuse_memory_client.flush() + + root_span = _finished_spans_by_name(memory_exporter, "root")[0] + child_spans = _finished_spans_by_name(memory_exporter, "child_step") + + assert len(child_spans) == 2 + assert all(child.parent is not None for child in child_spans) + assert all( + child.parent.span_id == root_span.context.span_id for child in child_spans + ) + assert all( + child.context.trace_id == root_span.context.trace_id for child in child_spans + ) + assert LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT not in root_span.attributes + + +@pytest.mark.asyncio +@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") +async def test_streaming_response_preserves_context_without_output_capture( + langfuse_memory_client, memory_exporter +): + class StreamingResponse: + def __init__(self, body_iterator): + self.body_iterator = body_iterator + + @observe(name="stream_step") + async def stream_step(index: int) -> str: + return f"chunk_{index}" + + async def body(): + for index in range(2): + yield await stream_step(index) + + @observe(name="endpoint", capture_output=False) + async def endpoint(): + return StreamingResponse(body()) + + response = await endpoint() + + assert memory_exporter.get_finished_spans() == [] + + assert [item async for item in response.body_iterator] == ["chunk_0", "chunk_1"] + assert response.body_iterator.items == [] + + langfuse_memory_client.flush() + + endpoint_span = _finished_spans_by_name(memory_exporter, "endpoint")[0] + step_spans = _finished_spans_by_name(memory_exporter, "stream_step") + + assert len(step_spans) == 2 + assert all(step.parent is not None for step in step_spans) + assert all( + step.parent.span_id == endpoint_span.context.span_id for step in step_spans + ) + assert all( + step.context.trace_id == endpoint_span.context.trace_id for step in step_spans + ) + assert LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT not in endpoint_span.attributes From cd9812c0e9e14e4943d2852ec95f45e1b695bc82 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 20 Apr 2026 15:04:05 +0200 Subject: [PATCH 258/296] ci: use explicit version for `fetch-metadata` version comment (#1633) ci: use explicit version --- .github/workflows/dependabot-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index aa321ee4d..8eddf89f8 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3 + uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs From ca2c4c1e0584a30c9230b4e0c165b55d03342985 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:20:33 +0200 Subject: [PATCH 259/296] fix(observe): preserve streaming context without output capture (#1634) --- langfuse/_client/observe.py | 147 +++++++++++++++------ tests/unit/test_observe.py | 247 ++++++++++++++++++++++++++++++++++-- 2 files changed, 343 insertions(+), 51 deletions(-) diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py index 3ada60bc9..64882a20f 100644 --- a/langfuse/_client/observe.py +++ b/langfuse/_client/observe.py @@ -563,10 +563,56 @@ def __init__( self.span = span self.capture_output = capture_output self.transform_fn = transform_fn + self._span_ended = False def __iter__(self) -> "_ContextPreservedSyncGeneratorWrapper": return self + def _finalize(self) -> None: + if self._span_ended: + return + + if self.capture_output: + output: Any = self.items + + if self.transform_fn is not None: + output = self.transform_fn(self.items) + + elif all(isinstance(item, str) for item in self.items): + output = "".join(self.items) + + self.span.update(output=output) + + self.span.end() + self._span_ended = True + + def _finalize_with_error(self, error: BaseException) -> None: + if self._span_ended: + return + + self.span.update( + level="ERROR", status_message=str(error) or type(error).__name__ + ).end() + self._span_ended = True + + def close(self) -> None: + if self._span_ended: + return + + try: + self.context.run(self.generator.close) + except (Exception, asyncio.CancelledError) as error: + self._finalize_with_error(error) + raise + else: + self._finalize() + + def __del__(self) -> None: + try: + self.close() + except BaseException: + pass + def __next__(self) -> Any: try: # Run the generator's __next__ in the preserved context @@ -577,27 +623,11 @@ def __next__(self) -> Any: return item except StopIteration: - # Handle output and span cleanup when generator is exhausted - if self.capture_output: - output: Any = self.items - - if self.transform_fn is not None: - output = self.transform_fn(self.items) - - elif all(isinstance(item, str) for item in self.items): - output = "".join(self.items) - - self.span.update(output=output) - - self.span.end() - + self._finalize() raise # Re-raise StopIteration except (Exception, asyncio.CancelledError) as e: - self.span.update( - level="ERROR", status_message=str(e) or type(e).__name__ - ).end() - + self._finalize_with_error(e) raise @@ -628,22 +658,77 @@ def __init__( self.span = span self.capture_output = capture_output self.transform_fn = transform_fn + self._span_ended = False def __aiter__(self) -> "_ContextPreservedAsyncGeneratorWrapper": return self + def _finalize(self) -> None: + if self._span_ended: + return + + if self.capture_output: + output: Any = self.items + + if self.transform_fn is not None: + output = self.transform_fn(self.items) + + elif all(isinstance(item, str) for item in self.items): + output = "".join(self.items) + + self.span.update(output=output) + + self.span.end() + self._span_ended = True + + def _finalize_with_error(self, error: BaseException) -> None: + if self._span_ended: + return + + self.span.update( + level="ERROR", status_message=str(error) or type(error).__name__ + ).end() + self._span_ended = True + + async def aclose(self) -> None: + if self._span_ended: + return + + try: + try: + await asyncio.create_task( + self.generator.aclose(), + context=self.context, + ) # type: ignore + except TypeError: + await self.context.run(asyncio.create_task, self.generator.aclose()) + except (Exception, asyncio.CancelledError) as error: + self._finalize_with_error(error) + raise + else: + self._finalize() + + async def close(self) -> None: + await self.aclose() + + def __del__(self) -> None: + self._finalize() + async def __anext__(self) -> Any: try: # Run the generator's __anext__ in the preserved context try: - # Python 3.10+ approach with context parameter + # Python 3.11+ approach with explicit task context item = await asyncio.create_task( self.generator.__anext__(), # type: ignore context=self.context, ) # type: ignore except TypeError: - # Python < 3.10 fallback - context parameter not supported - item = await self.generator.__anext__() + # Python 3.10 fallback - create the task inside the preserved context. + item = await self.context.run( + asyncio.create_task, + self.generator.__anext__(), # type: ignore + ) if self.capture_output: self.items.append(item) @@ -651,24 +736,8 @@ async def __anext__(self) -> Any: return item except StopAsyncIteration: - # Handle output and span cleanup when generator is exhausted - if self.capture_output: - output: Any = self.items - - if self.transform_fn is not None: - output = self.transform_fn(self.items) - - elif all(isinstance(item, str) for item in self.items): - output = "".join(self.items) - - self.span.update(output=output) - - self.span.end() - + self._finalize() raise # Re-raise StopAsyncIteration except (Exception, asyncio.CancelledError) as e: - self.span.update( - level="ERROR", status_message=str(e) or type(e).__name__ - ).end() - + self._finalize_with_error(e) raise diff --git a/tests/unit/test_observe.py b/tests/unit/test_observe.py index 94a2cbb83..24f79c3fc 100644 --- a/tests/unit/test_observe.py +++ b/tests/unit/test_observe.py @@ -1,25 +1,47 @@ +import asyncio +import contextvars +import gc import sys +from typing import Any, AsyncGenerator, Generator, cast import pytest from langfuse import observe from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.observe import ( + _ContextPreservedAsyncGeneratorWrapper, + _ContextPreservedSyncGeneratorWrapper, +) -def _finished_spans_by_name(memory_exporter, name: str): +class SpanRecorder: + def __init__(self) -> None: + self.ended = 0 + self.updates: list[dict[str, Any]] = [] + + def update(self, **kwargs: Any) -> "SpanRecorder": + self.updates.append(kwargs) + return self + + def end(self) -> "SpanRecorder": + self.ended += 1 + return self + + +def _finished_spans_by_name(memory_exporter: Any, name: str) -> list[Any]: return [span for span in memory_exporter.get_finished_spans() if span.name == name] def test_sync_generator_preserves_context_without_output_capture( - langfuse_memory_client, memory_exporter -): + langfuse_memory_client: Any, memory_exporter: Any +) -> None: @observe(name="child_step") def child_step(index: int) -> str: return f"item_{index}" @observe(name="root", capture_output=False) - def root(): - def body(): + def root() -> Generator[str, None, None]: + def body() -> Generator[str, None, None]: for index in range(2): yield child_step(index) @@ -30,7 +52,7 @@ def body(): assert memory_exporter.get_finished_spans() == [] assert list(generator) == ["item_0", "item_1"] - assert generator.items == [] + assert cast(Any, generator).items == [] langfuse_memory_client.flush() @@ -51,22 +73,22 @@ def body(): @pytest.mark.asyncio @pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") async def test_streaming_response_preserves_context_without_output_capture( - langfuse_memory_client, memory_exporter -): + langfuse_memory_client: Any, memory_exporter: Any +) -> None: class StreamingResponse: - def __init__(self, body_iterator): + def __init__(self, body_iterator: AsyncGenerator[str, None]) -> None: self.body_iterator = body_iterator @observe(name="stream_step") async def stream_step(index: int) -> str: return f"chunk_{index}" - async def body(): + async def body() -> AsyncGenerator[str, None]: for index in range(2): yield await stream_step(index) @observe(name="endpoint", capture_output=False) - async def endpoint(): + async def endpoint() -> StreamingResponse: return StreamingResponse(body()) response = await endpoint() @@ -74,7 +96,7 @@ async def endpoint(): assert memory_exporter.get_finished_spans() == [] assert [item async for item in response.body_iterator] == ["chunk_0", "chunk_1"] - assert response.body_iterator.items == [] + assert cast(Any, response.body_iterator).items == [] langfuse_memory_client.flush() @@ -90,3 +112,204 @@ async def endpoint(): step.context.trace_id == endpoint_span.context.trace_id for step in step_spans ) assert LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT not in endpoint_span.attributes + + +def test_sync_generator_wrapper_close_ends_span_without_exhaustion() -> None: + def generator() -> Generator[str, None, None]: + yield "item_0" + yield "item_1" + + span = SpanRecorder() + wrapper = _ContextPreservedSyncGeneratorWrapper( + generator(), + contextvars.copy_context(), + cast(Any, span), + False, + None, + ) + + assert next(wrapper) == "item_0" + + wrapper.close() + wrapper.close() + + assert span.ended == 1 + assert span.updates == [] + + +def test_sync_generator_wrapper_close_preserves_context() -> None: + marker = contextvars.ContextVar("marker", default="ambient") + seen: list[str] = [] + + def generator() -> Generator[str, None, None]: + try: + yield "item_0" + yield "item_1" + finally: + seen.append(marker.get()) + + span = SpanRecorder() + context = contextvars.copy_context() + context.run(marker.set, "preserved") + wrapper = _ContextPreservedSyncGeneratorWrapper( + generator(), + context, + cast(Any, span), + False, + None, + ) + + assert next(wrapper) == "item_0" + marker.set("ambient-now") + + wrapper.close() + + assert seen == ["preserved"] + assert span.ended == 1 + + +def test_sync_generator_wrapper_del_ends_span_when_abandoned() -> None: + def generator() -> Generator[str, None, None]: + yield "item_0" + yield "item_1" + + span = SpanRecorder() + wrapper = _ContextPreservedSyncGeneratorWrapper( + generator(), + contextvars.copy_context(), + cast(Any, span), + False, + None, + ) + + assert next(wrapper) == "item_0" + + del wrapper + gc.collect() + + assert span.ended == 1 + assert span.updates == [] + + +@pytest.mark.asyncio +async def test_async_generator_wrapper_aclose_ends_span_without_exhaustion() -> None: + async def generator() -> AsyncGenerator[str, None]: + yield "item_0" + yield "item_1" + + span = SpanRecorder() + wrapper = _ContextPreservedAsyncGeneratorWrapper( + generator(), + contextvars.copy_context(), + cast(Any, span), + False, + None, + ) + + assert await wrapper.__anext__() == "item_0" + + await wrapper.aclose() + await wrapper.close() + + assert span.ended == 1 + assert span.updates == [] + + +@pytest.mark.asyncio +async def test_async_generator_wrapper_aclose_preserves_context() -> None: + marker = contextvars.ContextVar("marker", default="ambient") + seen: list[str] = [] + + async def generator() -> AsyncGenerator[str, None]: + try: + yield "item_0" + yield "item_1" + finally: + seen.append(marker.get()) + + span = SpanRecorder() + context = contextvars.copy_context() + context.run(marker.set, "preserved") + wrapper = _ContextPreservedAsyncGeneratorWrapper( + generator(), + context, + cast(Any, span), + False, + None, + ) + + assert await wrapper.__anext__() == "item_0" + marker.set("ambient-now") + + await wrapper.aclose() + + assert seen == ["preserved"] + assert span.ended == 1 + + +@pytest.mark.asyncio +async def test_async_generator_wrapper_fallback_preserves_context( + monkeypatch: pytest.MonkeyPatch, +) -> None: + marker = contextvars.ContextVar("marker", default="ambient") + seen: list[str] = [] + original_create_task = asyncio.create_task + + def create_task_with_type_error(*args: Any, **kwargs: Any) -> asyncio.Task[Any]: + if "context" in kwargs: + raise TypeError("context argument unsupported") + + return original_create_task(*args, **kwargs) + + monkeypatch.setattr(asyncio, "create_task", create_task_with_type_error) + + async def generator() -> AsyncGenerator[str, None]: + try: + yield marker.get() + yield "item_1" + finally: + seen.append(marker.get()) + + span = SpanRecorder() + context = contextvars.copy_context() + context.run(marker.set, "preserved") + wrapper = _ContextPreservedAsyncGeneratorWrapper( + generator(), + context, + cast(Any, span), + False, + None, + ) + + assert await wrapper.__anext__() == "preserved" + marker.set("ambient-now") + + await wrapper.aclose() + + assert seen == ["preserved"] + assert span.ended == 1 + + +@pytest.mark.asyncio +async def test_async_generator_wrapper_del_ends_span_when_abandoned() -> None: + async def generator() -> AsyncGenerator[str, None]: + yield "item_0" + yield "item_1" + + span = SpanRecorder() + wrapper = _ContextPreservedAsyncGeneratorWrapper( + generator(), + contextvars.copy_context(), + cast(Any, span), + False, + None, + ) + + assert await wrapper.__anext__() == "item_0" + + del wrapper + gc.collect() + await asyncio.sleep(0) + + assert span.ended == 1 + assert span.updates == [] From 7f15dbfa444f242847d48a44878cc8b30f7ca227 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:19:31 +0200 Subject: [PATCH 260/296] fix(langchain): preserve LangGraph control flow traces (#1632) --- langfuse/langchain/CallbackHandler.py | 442 ++++++++++++++++--- tests/unit/test_langchain.py | 599 ++++++++++++++++++++++++++ 2 files changed, 983 insertions(+), 58 deletions(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index 80b7114e5..1349f6ae0 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -1,4 +1,6 @@ +from collections import OrderedDict from contextvars import Token +from dataclasses import dataclass, field from typing import ( Any, Dict, @@ -84,6 +86,8 @@ LANGSMITH_TAG_HIDDEN: str = "langsmith:hidden" CONTROL_FLOW_EXCEPTION_TYPES: Set[Type[BaseException]] = set() +LANGGRAPH_COMMAND_TYPE: Optional[Type[Any]] = None +MAX_PENDING_RESUME_TRACE_CONTEXTS = 1024 try: from langgraph.errors import GraphBubbleUp @@ -92,6 +96,51 @@ except ImportError: pass +try: + from langgraph.types import Command as LangGraphCommand + + LANGGRAPH_COMMAND_TYPE = LangGraphCommand +except ImportError: + pass + + +@dataclass +class _RunState: + parent_run_id: Optional[UUID] + root_run_id: UUID + + +@dataclass +class _RootRunState: + run_ids: Set[UUID] = field(default_factory=set) + resume_key: Optional[str] = None + propagation_context_manager: Optional[_AgnosticContextManager] = None + + +class _PendingResumeTraceContextStore: + def __init__(self, max_size: int) -> None: + self._max_size = max_size + self._contexts: OrderedDict[str, TraceContext] = OrderedDict() + + def store(self, *, resume_key: str, trace_context: TraceContext) -> None: + self._contexts[resume_key] = trace_context + self._contexts.move_to_end(resume_key) + + if len(self._contexts) > self._max_size: + self._contexts.popitem(last=False) + + def take(self, resume_key: str) -> Optional[TraceContext]: + return self._contexts.pop(resume_key, None) + + def __contains__(self, resume_key: str) -> bool: + return resume_key in self._contexts + + def __len__(self) -> int: + return len(self._contexts) + + def keys(self) -> List[str]: + return list(self._contexts.keys()) + class LangchainCallbackHandler(LangchainBaseCallbackHandler): def __init__( @@ -133,9 +182,12 @@ def __init__( self._context_tokens: Dict[UUID, Token] = {} self._prompt_to_parent_run_map: Dict[UUID, Any] = {} self._updated_completion_start_time_memo: Set[UUID] = set() - self._propagation_context_manager: Optional[_AgnosticContextManager] = None self._trace_context = trace_context - self._child_to_parent_run_id_map: Dict[UUID, Optional[UUID]] = {} + self._pending_resume_trace_contexts = _PendingResumeTraceContextStore( + MAX_PENDING_RESUME_TRACE_CONTEXTS + ) + self._run_states: Dict[UUID, _RunState] = {} + self._root_run_states: Dict[UUID, _RootRunState] = {} self.last_trace_id: Optional[str] = None @@ -161,6 +213,156 @@ def on_llm_new_token( self._updated_completion_start_time_memo.add(run_id) + def _get_langgraph_resume_key( + self, metadata: Optional[Dict[str, Any]] + ) -> Optional[str]: + thread_id = metadata.get("thread_id") if metadata else None + + if thread_id is None: + return None + + return str(thread_id) + + def _track_run( + self, + *, + run_id: UUID, + parent_run_id: Optional[UUID], + metadata: Optional[Dict[str, Any]] = None, + ) -> None: + if run_id in self._run_states: + return + + if parent_run_id is None: + root_run_id = run_id + self._root_run_states[root_run_id] = _RootRunState( + run_ids={run_id}, + resume_key=self._get_langgraph_resume_key(metadata), + ) + else: + parent_state = self._run_states.get(parent_run_id) + root_run_id = ( + parent_state.root_run_id if parent_state is not None else parent_run_id + ) + root_run_state = self._root_run_states.setdefault( + root_run_id, _RootRunState() + ) + root_run_state.run_ids.add(run_id) + + self._run_states[run_id] = _RunState( + parent_run_id=parent_run_id, + root_run_id=root_run_id, + ) + + def _get_run_state(self, run_id: UUID) -> Optional[_RunState]: + return self._run_states.get(run_id) + + def _get_root_run_state(self, run_id: UUID) -> Optional[_RootRunState]: + run_state = self._get_run_state(run_id) + + if run_state is None: + return None + + return self._root_run_states.get(run_state.root_run_id) + + def _pop_root_run_resume_key(self, run_id: UUID) -> Optional[str]: + root_run_state = self._get_root_run_state(run_id) + + if root_run_state is None: + return None + + resume_key = root_run_state.resume_key + root_run_state.resume_key = None + + return resume_key + + def _get_parent_run_id(self, run_id: UUID) -> Optional[UUID]: + run_state = self._get_run_state(run_id) + return run_state.parent_run_id if run_state is not None else None + + def _is_langgraph_resume(self, inputs: Any) -> bool: + return ( + LANGGRAPH_COMMAND_TYPE is not None + and isinstance(inputs, LANGGRAPH_COMMAND_TYPE) + and getattr(inputs, "resume", None) is not None + ) + + def _store_resume_trace_context( + self, *, resume_key: str, trace_context: TraceContext + ) -> None: + self._pending_resume_trace_contexts.store( + resume_key=resume_key, trace_context=trace_context + ) + + def _take_root_trace_context( + self, *, inputs: Any, metadata: Optional[Dict[str, Any]] + ) -> tuple[Optional[str], Optional[TraceContext]]: + if self._trace_context is not None: + return None, self._trace_context + + current_span_context = trace.get_current_span().get_span_context() + + # Only reuse the pending resume context when this callback run has no active + # parent span of its own. Nested callbacks should attach normally. + if current_span_context.is_valid: + return None, None + + # Only explicit LangGraph resumes should consume pending trace linkage. + if not self._is_langgraph_resume(inputs): + return None, None + + resume_key = self._get_langgraph_resume_key(metadata) + if resume_key is None: + return None, None + + return resume_key, self._pending_resume_trace_contexts.take(resume_key) + + def _restore_root_trace_context( + self, *, resume_key: Optional[str], trace_context: Optional[TraceContext] + ) -> None: + if self._trace_context is not None: + return + + if resume_key is None or trace_context is None: + return + + # Span creation failed after we consumed the pending linkage, so put it + # back and let the next retry resume the interrupted trace correctly. + self._store_resume_trace_context( + resume_key=resume_key, trace_context=trace_context + ) + + def _clear_root_run_resume_key(self, run_id: UUID) -> None: + # Keep the pending interrupt context until an explicit Command(resume=...) + # arrives. A separate root run on the same thread_id is not a resume. + self._pop_root_run_resume_key(run_id) + + def _persist_resume_trace_context(self, *, run_id: UUID, observation: Any) -> None: + if self._trace_context is not None: + return + + resume_key = self._pop_root_run_resume_key(run_id) + if resume_key is None: + return + + self._store_resume_trace_context( + resume_key=resume_key, + trace_context={ + "trace_id": observation.trace_id, + "parent_span_id": observation.id, + }, + ) + + def _get_error_level_and_status_message( + self, error: BaseException + ) -> tuple[Literal["DEFAULT", "ERROR"], str]: + # LangGraph uses GraphBubbleUp subclasses for expected control flow such as + # interrupts and handoffs, so they should stay visible without being errors. + if any(isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES): + return "DEFAULT", str(error) or type(error).__name__ + + return "ERROR", str(error) + def _get_observation_type_from_serialized( self, serialized: Optional[Dict[str, Any]], callback_type: str, **kwargs: Any ) -> Union[ @@ -256,15 +458,29 @@ def on_retriever_error( observation = self._detach_observation(run_id) if observation is not None: + level, status_message = self._get_error_level_and_status_message(error) observation.update( - level="ERROR", - status_message=str(error), + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + level, + ), + status_message=status_message, input=kwargs.get("inputs"), cost_details={"total": 0}, ).end() + if parent_run_id is None and level == "DEFAULT": + self._persist_resume_trace_context( + run_id=run_id, observation=observation + ) + elif parent_run_id is None: + self._clear_root_run_resume_key(run_id) + except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._reset(run_id) def _parse_langfuse_trace_attributes( self, *, metadata: Optional[Dict[str, Any]], tags: Optional[List[str]] @@ -333,7 +549,7 @@ def _get_langchain_observation_metadata( def on_chain_start( self, serialized: Optional[Dict[str, Any]], - inputs: Dict[str, Any], + inputs: Any, *, run_id: UUID, parent_run_id: Optional[UUID] = None, @@ -341,7 +557,11 @@ def on_chain_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id, metadata=metadata) + + span = None + resume_key = None + trace_context = None try: self._log_debug_event( @@ -369,7 +589,7 @@ def on_chain_start( metadata=metadata, tags=tags ) - self._propagation_context_manager = propagate_attributes( + propagation_context_manager = propagate_attributes( user_id=parsed_trace_attributes.get("user_id", None), session_id=parsed_trace_attributes.get("session_id", None), tags=parsed_trace_attributes.get("tags", None), @@ -377,12 +597,21 @@ def on_chain_start( trace_name=parsed_trace_attributes.get("trace_name", None), ) - self._propagation_context_manager.__enter__() + root_run_state = self._get_root_run_state(run_id) + if root_run_state is not None: + root_run_state.propagation_context_manager = ( + propagation_context_manager + ) + + propagation_context_manager.__enter__() obs = self._get_parent_observation(parent_run_id) if isinstance(obs, Langfuse): + resume_key, trace_context = self._take_root_trace_context( + inputs=inputs, metadata=metadata + ) span = obs.start_observation( - trace_context=self._trace_context, + trace_context=trace_context, name=span_name, as_type=observation_type, metadata=span_metadata, @@ -409,6 +638,13 @@ def on_chain_start( self.last_trace_id = self._runs[run_id].trace_id except Exception as e: + if span is None: + self._restore_root_trace_context( + resume_key=resume_key, trace_context=trace_context + ) + if parent_run_id is None: + self._exit_propagation_context(run_id) + self._reset(run_id) langfuse_logger.exception(e) def _register_langfuse_prompt( @@ -513,7 +749,7 @@ def on_agent_action( **kwargs: Any, ) -> Any: """Run on agent action.""" - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id) try: self._log_debug_event( @@ -586,7 +822,8 @@ def on_chain_end( ) if parent_run_id is None: - self._exit_propagation_context() + self._clear_root_run_resume_key(run_id) + self._exit_propagation_context(run_id) span.end() @@ -597,8 +834,8 @@ def on_chain_end( finally: if parent_run_id is None: - self._exit_propagation_context() - self._reset() + self._exit_propagation_context(run_id) + self._reset(run_id) def on_chain_error( self, @@ -611,10 +848,7 @@ def on_chain_error( ) -> None: try: self._log_debug_event("on_chain_error", run_id, parent_run_id, error=error) - if any(isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES): - level = None - else: - level = "ERROR" + level, status_message = self._get_error_level_and_status_message(error) observation = self._detach_observation(run_id) @@ -624,13 +858,19 @@ def on_chain_error( Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], level, ), - status_message=str(error) if level else None, + status_message=status_message, input=kwargs.get("inputs"), cost_details={"total": 0}, ) if parent_run_id is None: - self._exit_propagation_context() + if level == "DEFAULT": + self._persist_resume_trace_context( + run_id=run_id, observation=observation + ) + else: + self._clear_root_run_resume_key(run_id) + self._exit_propagation_context(run_id) observation.end() @@ -638,8 +878,8 @@ def on_chain_error( langfuse_logger.exception(e) finally: if parent_run_id is None: - self._exit_propagation_context() - self._reset() + self._exit_propagation_context(run_id) + self._reset(run_id) def on_chat_model_start( self, @@ -652,7 +892,7 @@ def on_chat_model_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id, metadata=metadata) try: self._log_debug_event( @@ -686,7 +926,7 @@ def on_llm_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id, metadata=metadata) try: self._log_debug_event( @@ -715,7 +955,7 @@ def on_tool_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id, metadata=metadata) try: self._log_debug_event( @@ -739,13 +979,24 @@ def on_tool_start( serialized, "tool", **kwargs ) - span = self._get_parent_observation(parent_run_id).start_observation( - name=self.get_langchain_run_name(serialized, **kwargs), - as_type=observation_type, - input=input_str, - metadata=meta, - level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, - ) + parent_observation = self._get_parent_observation(parent_run_id) + if isinstance(parent_observation, Langfuse): + span = parent_observation.start_observation( + trace_context=self._trace_context, + name=self.get_langchain_run_name(serialized, **kwargs), + as_type=observation_type, + input=input_str, + metadata=meta, + level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, + ) + else: + span = parent_observation.start_observation( + name=self.get_langchain_run_name(serialized, **kwargs), + as_type=observation_type, + input=input_str, + metadata=meta, + level="DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None, + ) self._attach_observation(run_id, span) @@ -763,7 +1014,7 @@ def on_retriever_start( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id, metadata=metadata) try: self._log_debug_event( @@ -780,16 +1031,30 @@ def on_retriever_start( observation_type = self._get_observation_type_from_serialized( serialized, "retriever", **kwargs ) - span = self._get_parent_observation(parent_run_id).start_observation( - name=span_name, - as_type=observation_type, - metadata=span_metadata, - input=query, - level=cast( - Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], - span_level, - ), - ) + parent_observation = self._get_parent_observation(parent_run_id) + if isinstance(parent_observation, Langfuse): + span = parent_observation.start_observation( + trace_context=self._trace_context, + name=span_name, + as_type=observation_type, + metadata=span_metadata, + input=query, + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + span_level, + ), + ) + else: + span = parent_observation.start_observation( + name=span_name, + as_type=observation_type, + metadata=span_metadata, + input=query, + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + span_level, + ), + ) self._attach_observation(run_id, span) @@ -811,6 +1076,8 @@ def on_retriever_end( observation = self._detach_observation(run_id) if observation is not None: + if parent_run_id is None: + self._clear_root_run_resume_key(run_id) observation.update( output=documents, input=kwargs.get("inputs"), @@ -818,6 +1085,9 @@ def on_retriever_end( except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._reset(run_id) def on_tool_end( self, @@ -833,6 +1103,8 @@ def on_tool_end( observation = self._detach_observation(run_id) if observation is not None: + if parent_run_id is None: + self._clear_root_run_resume_key(run_id) observation.update( output=output, input=kwargs.get("inputs"), @@ -840,6 +1112,9 @@ def on_tool_end( except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._reset(run_id) def on_tool_error( self, @@ -854,15 +1129,29 @@ def on_tool_error( observation = self._detach_observation(run_id) if observation is not None: + level, status_message = self._get_error_level_and_status_message(error) observation.update( - status_message=str(error), - level="ERROR", + status_message=status_message, + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + level, + ), input=kwargs.get("inputs"), cost_details={"total": 0}, ).end() + if parent_run_id is None and level == "DEFAULT": + self._persist_resume_trace_context( + run_id=run_id, observation=observation + ) + elif parent_run_id is None: + self._clear_root_run_resume_key(run_id) + except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._reset(run_id) def __on_llm_action( self, @@ -874,7 +1163,7 @@ def __on_llm_action( metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> None: - self._child_to_parent_run_id_map[run_id] = parent_run_id + self._track_run(run_id=run_id, parent_run_id=parent_run_id, metadata=metadata) try: tools = kwargs.get("invocation_params", {}).get("tools", None) @@ -898,8 +1187,8 @@ def __on_llm_action( self._deregister_langfuse_prompt(current_parent_run_id) break else: - current_parent_run_id = self._child_to_parent_run_id_map.get( - current_parent_run_id, None + current_parent_run_id = self._get_parent_run_id( + current_parent_run_id ) content = { @@ -919,9 +1208,17 @@ def __on_llm_action( "prompt": registered_prompt, } - generation = self._get_parent_observation(parent_run_id).start_observation( - as_type="generation", **content - ) # type: ignore + parent_observation = self._get_parent_observation(parent_run_id) + if isinstance(parent_observation, Langfuse): + generation = parent_observation.start_observation( + trace_context=self._trace_context, + as_type="generation", + **content, + ) # type: ignore + else: + generation = parent_observation.start_observation( + as_type="generation", **content + ) # type: ignore self._attach_observation(run_id, generation) self.last_trace_id = self._runs[run_id].trace_id @@ -1034,7 +1331,8 @@ def on_llm_end( self._updated_completion_start_time_memo.discard(run_id) if parent_run_id is None: - self._reset() + self._clear_root_run_resume_key(run_id) + self._reset(run_id) def on_llm_error( self, @@ -1050,26 +1348,54 @@ def on_llm_error( generation = self._detach_observation(run_id) if generation is not None: + level, status_message = self._get_error_level_and_status_message(error) generation.update( - status_message=str(error), - level="ERROR", + status_message=status_message, + level=cast( + Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], + level, + ), input=kwargs.get("inputs"), cost_details={"total": 0}, ).end() + if parent_run_id is None and level == "DEFAULT": + self._persist_resume_trace_context( + run_id=run_id, observation=generation + ) + elif parent_run_id is None: + self._clear_root_run_resume_key(run_id) + except Exception as e: langfuse_logger.exception(e) + finally: + if parent_run_id is None: + self._reset(run_id) - def _reset(self) -> None: - self._child_to_parent_run_id_map = {} + def _reset(self, root_run_id: UUID) -> None: + run_state = self._get_run_state(root_run_id) + if run_state is None: + return - def _exit_propagation_context(self) -> None: - manager = self._propagation_context_manager + root_run_state = self._root_run_states.pop(run_state.root_run_id, None) + if root_run_state is None: + self._run_states.pop(root_run_id, None) + return + + for run_id in root_run_state.run_ids: + self._run_states.pop(run_id, None) + + def _exit_propagation_context(self, run_id: UUID) -> None: + root_run_state = self._get_root_run_state(run_id) + + if root_run_state is None: + return + manager = root_run_state.propagation_context_manager if manager is None: return - self._propagation_context_manager = None + root_run_state.propagation_context_manager = None manager.__exit__(None, None, None) def __join_tags_and_metadata( diff --git a/tests/unit/test_langchain.py b/tests/unit/test_langchain.py index 5d8406e9c..27298342c 100644 --- a/tests/unit/test_langchain.py +++ b/tests/unit/test_langchain.py @@ -1,3 +1,4 @@ +import importlib from contextvars import copy_context from unittest.mock import patch from uuid import uuid4 @@ -9,16 +10,36 @@ from langchain_core.outputs import ChatGeneration, ChatResult, Generation, LLMResult from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI, OpenAI +from opentelemetry import context as otel_context from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse.langchain import CallbackHandler +callback_handler_module = importlib.import_module("langfuse.langchain.CallbackHandler") + def _assert_parent_child(parent_span, child_span) -> None: assert child_span.parent is not None assert child_span.parent.span_id == parent_span.context.span_id +def _has_pending_resume_context(handler, resume_key: str) -> bool: + return resume_key in handler._pending_resume_trace_contexts + + +def _pending_resume_context_keys(handler) -> list[str]: + return handler._pending_resume_trace_contexts.keys() + + +def _get_root_resume_key(handler, root_run_id): + root_run_state = handler._root_run_states.get(root_run_id) + return None if root_run_state is None else root_run_state.resume_key + + +def _has_run_state(handler, run_id) -> bool: + return run_id in handler._run_states + + def test_chat_model_callback_exports_generation_span( langfuse_memory_client, get_span, json_attr ): @@ -249,3 +270,581 @@ def test_root_chain_exports_when_end_runs_in_copied_context( assert root_span.attributes[LangfuseOtelSpanAttributes.TRACE_NAME] == ( "async-root-trace" ) + + +def test_control_flow_errors_use_default_level_and_keep_status_message( + langfuse_memory_client, get_span, monkeypatch +): + class DummyControlFlowError(RuntimeError): + pass + + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + + handler = CallbackHandler() + + tool_run_id = uuid4() + retriever_run_id = uuid4() + llm_run_id = uuid4() + chain_run_id = uuid4() + + handler.on_tool_start( + {"name": "human_approval"}, + "{}", + run_id=tool_run_id, + ) + handler.on_tool_error( + DummyControlFlowError("tool interrupt"), + run_id=tool_run_id, + ) + + handler.on_retriever_start( + {"name": "knowledge_base"}, + "approval policy", + run_id=retriever_run_id, + ) + handler.on_retriever_error( + DummyControlFlowError("retriever bubble-up"), + run_id=retriever_run_id, + ) + + handler.on_llm_start( + {"name": "TestLLM"}, + ["need approval"], + run_id=llm_run_id, + invocation_params={}, + ) + handler.on_llm_error( + DummyControlFlowError("llm bubble-up"), + run_id=llm_run_id, + ) + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=chain_run_id, + ) + handler.on_chain_error( + DummyControlFlowError("graph interrupt"), + run_id=chain_run_id, + ) + + handler._langfuse_client.flush() + + for span_name, message in [ + ("human_approval", "tool interrupt"), + ("knowledge_base", "retriever bubble-up"), + ("TestLLM", "llm bubble-up"), + ("LangGraph", "graph interrupt"), + ]: + span = get_span(span_name) + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "DEFAULT" + ) + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + == message + ) + + +def test_control_flow_resume_uses_thread_keyed_explicit_resume_context( + memory_exporter, langfuse_memory_client, monkeypatch +): + class DummyControlFlowError(RuntimeError): + pass + + Command = pytest.importorskip("langgraph.types").Command + + context_token = otel_context.attach(otel_context.Context()) + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + + try: + handler = CallbackHandler() + + thread_one_interrupt_run_id = uuid4() + thread_two_interrupt_run_id = uuid4() + thread_one_fresh_run_id = uuid4() + thread_two_resume_run_id = uuid4() + thread_one_resume_run_id = uuid4() + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=thread_one_interrupt_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_error( + DummyControlFlowError("graph interrupt 1"), + run_id=thread_one_interrupt_run_id, + ) + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=thread_two_interrupt_run_id, + metadata={"thread_id": "thread-2"}, + ) + handler.on_chain_error( + DummyControlFlowError("graph interrupt 2"), + run_id=thread_two_interrupt_run_id, + ) + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["fresh invocation"]}, + run_id=thread_one_fresh_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_end( + {"messages": ["completed"]}, + run_id=thread_one_fresh_run_id, + ) + + handler.on_chain_start( + {"name": "LangGraph"}, + Command(resume={"approved": True}), + run_id=thread_two_resume_run_id, + metadata={"thread_id": "thread-2"}, + ) + handler.on_chain_end( + {"messages": ["approved"]}, + run_id=thread_two_resume_run_id, + ) + + handler.on_chain_start( + {"name": "LangGraph"}, + Command(resume={"approved": True}), + run_id=thread_one_resume_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_end( + {"messages": ["approved"]}, + run_id=thread_one_resume_run_id, + ) + + handler._langfuse_client.flush() + + root_spans = [ + span + for span in memory_exporter.get_finished_spans() + if span.name == "LangGraph" + ] + + assert len(root_spans) == 5 + spans_by_trace_id = {} + for span in root_spans: + spans_by_trace_id.setdefault(span.context.trace_id, []).append(span) + + assert sorted(len(spans) for spans in spans_by_trace_id.values()) == [1, 2, 2] + + resumed_trace_spans = [ + spans for spans in spans_by_trace_id.values() if len(spans) == 2 + ] + assert len(resumed_trace_spans) == 2 + + for spans in resumed_trace_spans: + initial_span = next(span for span in spans if span.parent is None) + resumed_span = next(span for span in spans if span.parent is not None) + assert resumed_span.parent.span_id == initial_span.context.span_id + + fresh_trace_spans = next( + spans for spans in spans_by_trace_id.values() if len(spans) == 1 + ) + assert fresh_trace_spans[0].parent is None + finally: + otel_context.detach(context_token) + + +def test_control_flow_resume_restores_context_after_failed_root_start( + memory_exporter, langfuse_memory_client, monkeypatch +): + class DummyControlFlowError(RuntimeError): + pass + + Command = pytest.importorskip("langgraph.types").Command + + context_token = otel_context.attach(otel_context.Context()) + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + + try: + handler = CallbackHandler() + + interrupt_run_id = uuid4() + failed_resume_run_id = uuid4() + successful_resume_run_id = uuid4() + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=interrupt_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_error( + DummyControlFlowError("graph interrupt"), + run_id=interrupt_run_id, + ) + + assert _has_pending_resume_context(handler, "thread-1") + + with patch.object( + handler._langfuse_client, + "start_observation", + side_effect=RuntimeError("trace create failed"), + ): + handler.on_chain_start( + {"name": "LangGraph"}, + Command(resume={"approved": True}), + run_id=failed_resume_run_id, + metadata={"thread_id": "thread-1"}, + ) + + assert _has_pending_resume_context(handler, "thread-1") + assert _get_root_resume_key(handler, failed_resume_run_id) is None + assert not _has_run_state(handler, failed_resume_run_id) + assert failed_resume_run_id not in handler._root_run_states + + handler.on_chain_start( + {"name": "LangGraph"}, + Command(resume={"approved": True}), + run_id=successful_resume_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_end( + {"messages": ["approved"]}, + run_id=successful_resume_run_id, + ) + + handler._langfuse_client.flush() + + root_spans = [ + span + for span in memory_exporter.get_finished_spans() + if span.name == "LangGraph" + ] + + assert len(root_spans) == 2 + + initial_span = next(span for span in root_spans if span.parent is None) + resumed_span = next(span for span in root_spans if span.parent is not None) + + assert resumed_span.parent.span_id == initial_span.context.span_id + finally: + otel_context.detach(context_token) + + +def test_control_flow_resume_ignores_non_resume_commands( + memory_exporter, langfuse_memory_client, monkeypatch +): + class DummyControlFlowError(RuntimeError): + pass + + Command = pytest.importorskip("langgraph.types").Command + + context_token = otel_context.attach(otel_context.Context()) + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + + try: + handler = CallbackHandler() + + interrupt_run_id = uuid4() + goto_run_id = uuid4() + resume_run_id = uuid4() + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=interrupt_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_error( + DummyControlFlowError("graph interrupt"), + run_id=interrupt_run_id, + ) + + handler.on_chain_start( + {"name": "LangGraph"}, + Command(goto="approval_node"), + run_id=goto_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_end( + {"messages": ["routed"]}, + run_id=goto_run_id, + ) + + assert _has_pending_resume_context(handler, "thread-1") + + handler.on_chain_start( + {"name": "LangGraph"}, + Command(resume={"approved": True}), + run_id=resume_run_id, + metadata={"thread_id": "thread-1"}, + ) + handler.on_chain_end( + {"messages": ["approved"]}, + run_id=resume_run_id, + ) + + handler._langfuse_client.flush() + + root_spans = [ + span + for span in memory_exporter.get_finished_spans() + if span.name == "LangGraph" + ] + + assert len(root_spans) == 3 + + spans_by_trace_id = {} + for span in root_spans: + spans_by_trace_id.setdefault(span.context.trace_id, []).append(span) + + assert sorted(len(spans) for spans in spans_by_trace_id.values()) == [1, 2] + + resumed_trace_spans = next( + spans for spans in spans_by_trace_id.values() if len(spans) == 2 + ) + initial_span = next(span for span in resumed_trace_spans if span.parent is None) + resumed_span = next( + span for span in resumed_trace_spans if span.parent is not None + ) + + assert resumed_span.parent.span_id == initial_span.context.span_id + finally: + otel_context.detach(context_token) + + +def test_root_reset_preserves_other_inflight_resume_keys( + memory_exporter, langfuse_memory_client, monkeypatch +): + class DummyControlFlowError(RuntimeError): + pass + + Command = pytest.importorskip("langgraph.types").Command + + context_token = otel_context.attach(otel_context.Context()) + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + + try: + handler = CallbackHandler() + root_one_context = copy_context() + root_two_context = copy_context() + + root_one_run_id = uuid4() + root_two_run_id = uuid4() + root_two_resume_run_id = uuid4() + + root_one_context.run( + handler.on_chain_start, + {"name": "LangGraph"}, + {"messages": ["completed"]}, + run_id=root_one_run_id, + metadata={"thread_id": "thread-1"}, + ) + root_two_context.run( + handler.on_chain_start, + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=root_two_run_id, + metadata={"thread_id": "thread-2"}, + ) + + assert _get_root_resume_key(handler, root_two_run_id) == "thread-2" + + root_one_context.run( + handler.on_chain_end, + {"messages": ["completed"]}, + run_id=root_one_run_id, + ) + + assert _get_root_resume_key(handler, root_two_run_id) == "thread-2" + + root_two_context.run( + handler.on_chain_error, + DummyControlFlowError("graph interrupt"), + run_id=root_two_run_id, + ) + + assert _has_pending_resume_context(handler, "thread-2") + + root_two_context.run( + handler.on_chain_start, + {"name": "LangGraph"}, + Command(resume={"approved": True}), + run_id=root_two_resume_run_id, + metadata={"thread_id": "thread-2"}, + ) + root_two_context.run( + handler.on_chain_end, + {"messages": ["approved"]}, + run_id=root_two_resume_run_id, + ) + + handler._langfuse_client.flush() + + root_spans = [ + span + for span in memory_exporter.get_finished_spans() + if span.name == "LangGraph" + ] + + assert len(root_spans) == 3 + + spans_by_trace_id = {} + for span in root_spans: + spans_by_trace_id.setdefault(span.context.trace_id, []).append(span) + + assert sorted(len(spans) for spans in spans_by_trace_id.values()) == [1, 2] + finally: + otel_context.detach(context_token) + + +def test_root_tool_and_retriever_runs_seed_resume_keys_and_cleanup( + langfuse_memory_client, monkeypatch +): + class DummyControlFlowError(RuntimeError): + pass + + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + + handler = CallbackHandler() + + tool_error_run_id = uuid4() + tool_end_run_id = uuid4() + retriever_run_id = uuid4() + + handler.on_tool_start( + {"name": "human_approval"}, + "{}", + run_id=tool_error_run_id, + metadata={"thread_id": "tool-error-thread"}, + ) + assert _get_root_resume_key(handler, tool_error_run_id) == "tool-error-thread" + + handler.on_tool_error( + DummyControlFlowError("tool interrupt"), + run_id=tool_error_run_id, + ) + + assert _has_pending_resume_context(handler, "tool-error-thread") + assert _get_root_resume_key(handler, tool_error_run_id) is None + assert not _has_run_state(handler, tool_error_run_id) + + handler.on_tool_start( + {"name": "human_approval"}, + "{}", + run_id=tool_end_run_id, + metadata={"thread_id": "tool-end-thread"}, + ) + assert _get_root_resume_key(handler, tool_end_run_id) == "tool-end-thread" + + handler.on_tool_end( + '{"approved": true}', + run_id=tool_end_run_id, + ) + + assert _get_root_resume_key(handler, tool_end_run_id) is None + assert not _has_run_state(handler, tool_end_run_id) + + handler.on_retriever_start( + {"name": "knowledge_base"}, + "approval policy", + run_id=retriever_run_id, + metadata={"thread_id": "retriever-thread"}, + ) + assert _get_root_resume_key(handler, retriever_run_id) == "retriever-thread" + + handler.on_retriever_error( + DummyControlFlowError("retriever interrupt"), + run_id=retriever_run_id, + ) + + assert _has_pending_resume_context(handler, "retriever-thread") + assert _get_root_resume_key(handler, retriever_run_id) is None + assert not _has_run_state(handler, retriever_run_id) + + +def test_pending_resume_contexts_are_capped(langfuse_memory_client, monkeypatch): + class DummyControlFlowError(RuntimeError): + pass + + monkeypatch.setattr( + callback_handler_module, + "CONTROL_FLOW_EXCEPTION_TYPES", + {DummyControlFlowError}, + ) + monkeypatch.setattr( + callback_handler_module, + "MAX_PENDING_RESUME_TRACE_CONTEXTS", + 4, + ) + + handler = CallbackHandler() + + for index in range(5): + run_id = uuid4() + thread_id = f"thread-{index}" + + handler.on_chain_start( + {"name": "LangGraph"}, + {"messages": ["need approval"]}, + run_id=run_id, + metadata={"thread_id": thread_id}, + ) + handler.on_chain_error( + DummyControlFlowError(f"graph interrupt {index}"), + run_id=run_id, + ) + + assert len(handler._pending_resume_trace_contexts) == 4 + assert _pending_resume_context_keys(handler) == [ + "thread-1", + "thread-2", + "thread-3", + "thread-4", + ] + + +def test_graphbubbleup_import_is_independent_from_command_import(): + real_import = __import__ + + def import_without_langgraph_command( + name, globals=None, locals=None, fromlist=(), level=0 + ): + if name == "langgraph.types": + raise ImportError("Command unavailable") + + return real_import(name, globals, locals, fromlist, level) + + with patch("builtins.__import__", side_effect=import_without_langgraph_command): + reloaded_module = importlib.reload(callback_handler_module) + assert reloaded_module.LANGGRAPH_COMMAND_TYPE is None + assert any( + exception_type.__name__ == "GraphBubbleUp" + for exception_type in reloaded_module.CONTROL_FLOW_EXCEPTION_TYPES + ) + + importlib.reload(callback_handler_module) From 7aa632f1f76fa10d3a7c3f38545caa66dc7d8564 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:27:14 +0200 Subject: [PATCH 261/296] fix(openai): preserve native v1 stream contract (#1627) --- langfuse/openai.py | 307 +++++++++++++++++++++++----- tests/unit/test_openai.py | 413 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 674 insertions(+), 46 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 16d293e73..1ce09f754 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -21,7 +21,7 @@ from collections import defaultdict from dataclasses import dataclass from datetime import datetime -from inspect import isclass +from inspect import isawaitable, isclass from typing import Any, Optional, cast from openai._types import NotGiven @@ -830,6 +830,191 @@ def _is_streaming_response(response: Any) -> bool: ) +_openai_stream_iter_hook_installed = False + + +def _install_openai_stream_iteration_hooks() -> None: + global _openai_stream_iter_hook_installed + + if not _is_openai_v1(): + return + + if not _openai_stream_iter_hook_installed: + original_iter = openai.Stream.__iter__ + original_aiter = openai.AsyncStream.__aiter__ + + def traced_iter(self: Any) -> Any: + try: + yield from original_iter(self) + finally: + finalize_once = getattr(self, "_langfuse_finalize_once", None) + if finalize_once is not None: + finalize_once() + + async def traced_aiter(self: Any) -> Any: + try: + async for item in original_aiter(self): + yield item + finally: + finalize_once = getattr(self, "_langfuse_finalize_once", None) + if finalize_once is not None: + await finalize_once() + + setattr(openai.Stream, "__iter__", traced_iter) + setattr(openai.AsyncStream, "__aiter__", traced_aiter) + _openai_stream_iter_hook_installed = True + + +def _finalize_stream_response( + *, + resource: OpenAiDefinition, + items: list[Any], + generation: LangfuseGeneration, + completion_start_time: Optional[datetime], +) -> None: + try: + model, completion, usage, metadata = ( + _extract_streamed_response_api_response(items) + if resource.object == "Responses" or resource.object == "AsyncResponses" + else _extract_streamed_openai_response(resource, items) + ) + + _create_langfuse_update( + completion, + generation, + completion_start_time, + model=model, + usage=usage, + metadata=metadata, + ) + except Exception: + pass + finally: + generation.end() + + +def _instrument_openai_stream( + *, + resource: OpenAiDefinition, + response: Any, + generation: LangfuseGeneration, +) -> Any: + if not hasattr(response, "_iterator"): + return LangfuseResponseGeneratorSync( + resource=resource, + response=response, + generation=generation, + ) + + items: list[Any] = [] + raw_iterator = response._iterator + completion_start_time: Optional[datetime] = None + is_finalized = False + close = response.close + + def finalize_once() -> None: + nonlocal is_finalized + if is_finalized: + return + + is_finalized = True + _finalize_stream_response( + resource=resource, + items=items, + generation=generation, + completion_start_time=completion_start_time, + ) + + response._langfuse_finalize_once = finalize_once # type: ignore[attr-defined] + + def traced_iterator() -> Any: + nonlocal completion_start_time + try: + for item in raw_iterator: + items.append(item) + + if completion_start_time is None: + completion_start_time = _get_timestamp() + + yield item + finally: + finalize_once() + + def traced_close() -> Any: + try: + return close() + finally: + finalize_once() + + response._iterator = traced_iterator() + response.close = traced_close + + return response + + +def _instrument_openai_async_stream( + *, + resource: OpenAiDefinition, + response: Any, + generation: LangfuseGeneration, +) -> Any: + if not hasattr(response, "_iterator"): + return LangfuseResponseGeneratorAsync( + resource=resource, + response=response, + generation=generation, + ) + + items: list[Any] = [] + raw_iterator = response._iterator + completion_start_time: Optional[datetime] = None + is_finalized = False + close = response.close + + async def finalize_once() -> None: + nonlocal is_finalized + if is_finalized: + return + + is_finalized = True + _finalize_stream_response( + resource=resource, + items=items, + generation=generation, + completion_start_time=completion_start_time, + ) + + response._langfuse_finalize_once = finalize_once # type: ignore[attr-defined] + + async def traced_iterator() -> Any: + nonlocal completion_start_time + try: + async for item in raw_iterator: + items.append(item) + + if completion_start_time is None: + completion_start_time = _get_timestamp() + + yield item + finally: + await finalize_once() + + async def traced_close() -> Any: + try: + return await close() + finally: + await finalize_once() + + async def traced_aclose() -> Any: + return await traced_close() + + response._iterator = traced_iterator() + response.close = traced_close + response.aclose = traced_aclose + + return response + + @_langfuse_wrapper def _wrap( open_ai_resource: OpenAiDefinition, wrapped: Any, args: Any, kwargs: Any @@ -863,7 +1048,13 @@ def _wrap( try: openai_response = wrapped(**arg_extractor.get_openai_args()) - if _is_streaming_response(openai_response): + if _is_openai_v1() and isinstance(openai_response, openai.Stream): + return _instrument_openai_stream( + resource=open_ai_resource, + response=openai_response, + generation=generation, + ) + elif _is_streaming_response(openai_response): return LangfuseResponseGeneratorSync( resource=open_ai_resource, response=openai_response, @@ -934,7 +1125,13 @@ async def _wrap_async( try: openai_response = await wrapped(**arg_extractor.get_openai_args()) - if _is_streaming_response(openai_response): + if _is_openai_v1() and isinstance(openai_response, openai.AsyncStream): + return _instrument_openai_async_stream( + resource=open_ai_resource, + response=openai_response, + generation=generation, + ) + elif _is_streaming_response(openai_response): return LangfuseResponseGeneratorAsync( resource=open_ai_resource, response=openai_response, @@ -994,6 +1191,7 @@ def register_tracing() -> None: register_tracing() +_install_openai_stream_iteration_hooks() class LangfuseResponseGeneratorSync: @@ -1010,6 +1208,7 @@ def __init__( self.response = response self.generation = generation self.completion_start_time: Optional[datetime] = None + self._is_finalized = False def __iter__(self) -> Any: try: @@ -1042,29 +1241,28 @@ def __enter__(self) -> Any: return self.__iter__() def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: - pass + self.close() - def _finalize(self) -> None: - try: - model, completion, usage, metadata = ( - _extract_streamed_response_api_response(self.items) - if self.resource.object == "Responses" - or self.resource.object == "AsyncResponses" - else _extract_streamed_openai_response(self.resource, self.items) - ) + def close(self) -> None: + close = getattr(self.response, "close", None) - _create_langfuse_update( - completion, - self.generation, - self.completion_start_time, - model=model, - usage=usage, - metadata=metadata, - ) - except Exception: - pass + try: + if callable(close): + close() finally: - self.generation.end() + self._finalize() + + def _finalize(self) -> None: + if self._is_finalized: + return + + self._is_finalized = True + _finalize_stream_response( + resource=self.resource, + items=self.items, + generation=self.generation, + completion_start_time=self.completion_start_time, + ) class LangfuseResponseGeneratorAsync: @@ -1081,6 +1279,7 @@ def __init__( self.response = response self.generation = generation self.completion_start_time: Optional[datetime] = None + self._is_finalized = False async def __aiter__(self) -> Any: try: @@ -1113,40 +1312,56 @@ async def __aenter__(self) -> Any: return self.__aiter__() async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: - pass + await self.aclose() async def _finalize(self) -> None: - try: - model, completion, usage, metadata = ( - _extract_streamed_response_api_response(self.items) - if self.resource.object == "Responses" - or self.resource.object == "AsyncResponses" - else _extract_streamed_openai_response(self.resource, self.items) - ) - - _create_langfuse_update( - completion, - self.generation, - self.completion_start_time, - model=model, - usage=usage, - metadata=metadata, - ) - except Exception: - pass - finally: - self.generation.end() + if self._is_finalized: + return + + self._is_finalized = True + _finalize_stream_response( + resource=self.resource, + items=self.items, + generation=self.generation, + completion_start_time=self.completion_start_time, + ) async def close(self) -> None: """Close the response and release the connection. Automatically called if the response body is read to completion. """ - await self.response.close() + close = getattr(self.response, "close", None) + aclose = getattr(self.response, "aclose", None) + + try: + if callable(close): + result = close() + if isawaitable(result): + await result + elif callable(aclose): + result = aclose() + if isawaitable(result): + await result + finally: + await self._finalize() async def aclose(self) -> None: """Close the response and release the connection. Automatically called if the response body is read to completion. """ - await self.response.aclose() + aclose = getattr(self.response, "aclose", None) + close = getattr(self.response, "close", None) + + try: + if callable(aclose): + result = aclose() + if isawaitable(result): + await result + elif callable(close): + result = close() + if isawaitable(result): + await result + finally: + await self._finalize() diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py index 6ef51ff54..ce69b1c76 100644 --- a/tests/unit/test_openai.py +++ b/tests/unit/test_openai.py @@ -1,12 +1,124 @@ +import asyncio from types import SimpleNamespace from unittest.mock import patch import pytest +import langfuse.openai as lf_openai_module from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse.openai import openai as lf_openai +class DummySyncResponse: + def __init__(self) -> None: + self.closed = False + + def close(self) -> None: + self.closed = True + + +class DummyAsyncResponse: + def __init__(self) -> None: + self.closed = False + + async def aclose(self) -> None: + self.closed = True + + +class DummyOpenAIStream(lf_openai.Stream): + def __init__(self, items, response) -> None: + self.response = response + self._iterator = iter(items) + + +class DummyOpenAIAsyncStream(lf_openai.AsyncStream): + def __init__(self, items, response) -> None: + self.response = response + self._iterator = self._stream(items) + + async def _stream(self, items): + for item in items: + yield item + + +class DummyGeneration: + def __init__(self) -> None: + self.end_calls = 0 + + def update(self, **kwargs): + return self + + def end(self) -> None: + self.end_calls += 1 + + +class DummyFallbackAsyncResponse: + def __init__(self) -> None: + self.close_calls = 0 + self.aclose_calls = 0 + + async def close(self) -> None: + self.close_calls += 1 + + async def aclose(self) -> None: + self.aclose_calls += 1 + + +def _make_chat_stream_chunks(): + usage = SimpleNamespace(prompt_tokens=3, completion_tokens=1, total_tokens=4) + + return [ + SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role="assistant", + content="2", + function_call=None, + tool_calls=None, + ), + finish_reason=None, + ) + ], + usage=None, + ), + SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role=None, + content=None, + function_call=None, + tool_calls=None, + ), + finish_reason="stop", + ) + ], + usage=usage, + ), + ] + + +def _make_single_chunk_stream(): + return SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role="assistant", + content="2", + function_call=None, + tool_calls=None, + ), + finish_reason="stop", + ) + ], + usage=None, + ) + + def test_chat_completion_exports_generation_span( langfuse_memory_client, get_span, json_attr ): @@ -161,6 +273,80 @@ def test_chat_completion_error_marks_generation_error(langfuse_memory_client, ge assert LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT not in span.attributes +def test_openai_stream_preserves_original_stream_contract( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.OpenAI(api_key="test") + raw_response = DummySyncResponse() + raw_stream = DummyOpenAIStream(_make_chat_stream_chunks(), raw_response) + + with patch.object(openai_client.chat.completions, "_post", return_value=raw_stream): + stream = openai_client.chat.completions.create( + name="unit-openai-native-stream", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + + assert stream is raw_stream + assert isinstance(stream, lf_openai.Stream) + assert stream.response is raw_response + + chunks = list(stream) + stream.close() + + assert len(chunks) == 2 + assert raw_response.closed is True + + langfuse_memory_client.flush() + span = get_span("unit-openai-native-stream") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME] + is not None + ) + assert span.attributes["langfuse.observation.metadata.finish_reason"] == "stop" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 3, + "completion_tokens": 1, + "total_tokens": 4, + } + + +def test_openai_stream_break_still_finalizes_generation( + langfuse_memory_client, get_span +): + openai_client = lf_openai.OpenAI(api_key="test") + raw_response = DummySyncResponse() + raw_stream = DummyOpenAIStream(_make_chat_stream_chunks(), raw_response) + + with patch.object(openai_client.chat.completions, "_post", return_value=raw_stream): + stream = openai_client.chat.completions.create( + name="unit-openai-native-stream-break", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + + for chunk in stream: + assert chunk.choices[0].delta.content == "2" + break + + assert raw_response.closed is False + + langfuse_memory_client.flush() + span = get_span("unit-openai-native-stream-break") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME] + is not None + ) + + @pytest.mark.asyncio async def test_async_chat_completion_exports_generation_span( langfuse_memory_client, get_span, json_attr @@ -206,6 +392,233 @@ async def test_async_chat_completion_exports_generation_span( } +@pytest.mark.asyncio +async def test_openai_async_stream_preserves_original_stream_contract( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.AsyncOpenAI(api_key="test") + raw_response = DummyAsyncResponse() + raw_stream = DummyOpenAIAsyncStream(_make_chat_stream_chunks(), raw_response) + + with patch.object(openai_client.chat.completions, "_post", return_value=raw_stream): + stream = await openai_client.chat.completions.create( + name="unit-openai-native-async-stream", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + + assert stream is raw_stream + assert isinstance(stream, lf_openai.AsyncStream) + assert stream.response is raw_response + assert hasattr(stream, "aclose") + + chunks = [] + async for chunk in stream: + chunks.append(chunk) + + await stream.aclose() + + assert len(chunks) == 2 + assert raw_response.closed is True + + langfuse_memory_client.flush() + span = get_span("unit-openai-native-async-stream") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME] + is not None + ) + assert span.attributes["langfuse.observation.metadata.finish_reason"] == "stop" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 3, + "completion_tokens": 1, + "total_tokens": 4, + } + + +@pytest.mark.asyncio +async def test_openai_async_stream_supports_anext( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.AsyncOpenAI(api_key="test") + raw_stream = DummyOpenAIAsyncStream( + _make_chat_stream_chunks(), DummyAsyncResponse() + ) + + with patch.object(openai_client.chat.completions, "_post", return_value=raw_stream): + stream = await openai_client.chat.completions.create( + name="unit-openai-native-async-anext", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + + first = await stream.__anext__() + second = await stream.__anext__() + + assert first.choices[0].delta.content == "2" + assert second.choices[0].finish_reason == "stop" + + with pytest.raises(StopAsyncIteration): + await stream.__anext__() + + langfuse_memory_client.flush() + span = get_span("unit-openai-native-async-anext") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME] + is not None + ) + assert span.attributes["langfuse.observation.metadata.finish_reason"] == "stop" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 3, + "completion_tokens": 1, + "total_tokens": 4, + } + + +@pytest.mark.asyncio +async def test_openai_async_stream_break_still_finalizes_generation( + langfuse_memory_client, get_span +): + openai_client = lf_openai.AsyncOpenAI(api_key="test") + raw_stream = DummyOpenAIAsyncStream( + _make_chat_stream_chunks(), DummyAsyncResponse() + ) + + with patch.object(openai_client.chat.completions, "_post", return_value=raw_stream): + stream = await openai_client.chat.completions.create( + name="unit-openai-native-async-stream-break", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + + async for chunk in stream: + assert chunk.choices[0].delta.content == "2" + break + + # Async generator finalizers are scheduled across event-loop turns. + for _ in range(5): + await asyncio.sleep(0) + + langfuse_memory_client.flush() + span = get_span("unit-openai-native-async-stream-break") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME] + is not None + ) + + +def test_fallback_sync_stream_finalizes_once(): + resource = SimpleNamespace(object="Completions", type="chat") + generation = DummyGeneration() + + def fallback_stream(): + yield _make_single_chunk_stream() + + wrapper = lf_openai_module.LangfuseResponseGeneratorSync( + resource=resource, + response=fallback_stream(), + generation=generation, + ) + + list(wrapper) + + with pytest.raises(StopIteration): + next(wrapper) + + assert generation.end_calls == 1 + + +def test_fallback_sync_stream_exit_finalizes_once(): + resource = SimpleNamespace(object="Completions", type="chat") + generation = DummyGeneration() + + def fallback_stream(): + yield _make_single_chunk_stream() + + wrapper = lf_openai_module.LangfuseResponseGeneratorSync( + resource=resource, + response=fallback_stream(), + generation=generation, + ) + + wrapper.__exit__(None, None, None) + + assert generation.end_calls == 1 + + +@pytest.mark.asyncio +async def test_fallback_async_stream_finalizes_once(): + resource = SimpleNamespace(object="Completions", type="chat") + generation = DummyGeneration() + + async def fallback_stream(): + yield _make_single_chunk_stream() + + wrapper = lf_openai_module.LangfuseResponseGeneratorAsync( + resource=resource, + response=fallback_stream(), + generation=generation, + ) + + async for _ in wrapper: + pass + + with pytest.raises(StopAsyncIteration): + await wrapper.__anext__() + + assert generation.end_calls == 1 + + +@pytest.mark.asyncio +async def test_fallback_async_stream_close_and_exit_finalize_once(): + resource = SimpleNamespace(object="Completions", type="chat") + generation = DummyGeneration() + response = DummyFallbackAsyncResponse() + + wrapper = lf_openai_module.LangfuseResponseGeneratorAsync( + resource=resource, + response=response, + generation=generation, + ) + + await wrapper.close() + await wrapper.__aexit__(None, None, None) + + assert generation.end_calls == 1 + assert response.close_calls == 1 + assert response.aclose_calls == 1 + + +@pytest.mark.asyncio +async def test_fallback_async_stream_aclose_finalizes_once(): + resource = SimpleNamespace(object="Completions", type="chat") + generation = DummyGeneration() + + async def fallback_stream(): + yield _make_single_chunk_stream() + + wrapper = lf_openai_module.LangfuseResponseGeneratorAsync( + resource=resource, + response=fallback_stream(), + generation=generation, + ) + + await wrapper.aclose() + + assert generation.end_calls == 1 + + def test_embedding_exports_dimensions_and_count( langfuse_memory_client, get_span, json_attr ): From 560eca93b6f19fd346146498e6e714a0d3e8ff8d Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 21 Apr 2026 11:30:35 +0000 Subject: [PATCH 262/296] chore: release v4.5.0 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dc892fb52..c88ea61b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.4.0b1" +version = "4.5.0" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 97b88a571..6c8616dfa 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-10T12:22:34.304101501Z" +exclude-newer = "2026-04-14T11:30:30.93971086Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.4.0b1" +version = "4.5.0" source = { editable = "." } dependencies = [ { name = "backoff" }, From d5ce2d27cbf8fd6f95f93e45b73295a1c3a8f8a7 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Tue, 21 Apr 2026 18:27:22 +0200 Subject: [PATCH 263/296] chore(ci): pin action version comments to immutable patch tags (#1636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tightens `# v6`/`# v3` floating-major comments on SHA-pinned actions to their exact patch-level tags (`v6.0.2`, `v6.2.0`, `v3.0.1`). Same SHAs, no behavior change — just removes ambiguity about which release the pin corresponds to, and keeps the version comment truthful if the upstream major ever moves. Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/codeql.yml | 2 +- .github/workflows/package-availability-check.yml | 2 +- .github/workflows/release.yml | 6 +++--- .github/workflows/zizmor.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a460a741..9b820059c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: linting: runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install uv and set Python version @@ -37,7 +37,7 @@ jobs: type-checking: runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install uv and set Python version @@ -78,7 +78,7 @@ jobs: name: Unit tests on Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install uv and set Python version @@ -141,7 +141,7 @@ jobs: name: ${{ matrix.job_name }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install uv and set Python version diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1834f6f10..290bfee87 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -55,7 +55,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/package-availability-check.yml b/.github/workflows/package-availability-check.yml index 3f43ddd21..836213f89 100644 --- a/.github/workflows/package-availability-check.yml +++ b/.github/workflows/package-availability-check.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies using pip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc81ae2a4..efff90e16 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: INPUTS_CONFIRM_MAJOR: ${{ inputs.confirm_major }} - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GH_ACCESS_TOKEN }} @@ -321,7 +321,7 @@ jobs: - name: Notify Slack on success if: success() - uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3 + uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 with: webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} webhook-type: incoming-webhook @@ -405,7 +405,7 @@ jobs: - name: Notify Slack on failure if: failure() - uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3 + uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 with: webhook: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} webhook-type: incoming-webhook diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 725619c3f..03d663f9d 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -22,7 +22,7 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Run zizmor From 9244e1402393b87efb04c1e0bfce796081dd5504 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:20:50 +0200 Subject: [PATCH 264/296] fix(experiments): flatten propagated metadata (#1641) --- langfuse/_client/attributes.py | 26 +++++ langfuse/_client/client.py | 14 ++- langfuse/_client/propagation.py | 50 +++++++--- tests/e2e/test_experiments.py | 59 +++++++++++ tests/unit/test_propagate_attributes.py | 125 ++++++++++++++++++------ 5 files changed, 224 insertions(+), 50 deletions(-) diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py index bfe37446f..d34e8e403 100644 --- a/langfuse/_client/attributes.py +++ b/langfuse/_client/attributes.py @@ -164,6 +164,32 @@ def _serialize(obj: Any) -> Optional[str]: return json.dumps(obj, cls=EventSerializer) +def _flatten_and_serialize_metadata_values( + metadata: Optional[Dict[str, Any]], +) -> Optional[Dict[str, str]]: + if metadata is None: + return None + + flattened_metadata: Dict[str, str] = {} + + def flatten_value(path: str, value: Any) -> None: + if isinstance(value, dict): + for nested_key, nested_value in value.items(): + flatten_value(f"{path}.{nested_key}", nested_value) + + return + + serialized_value = _serialize(value) + + if serialized_value is not None: + flattened_metadata[path] = serialized_value + + for key, value in metadata.items(): + flatten_value(str(key), value) + + return flattened_metadata + + def _flatten_and_serialize_metadata( metadata: Any, type: Literal["observation", "trace"] ) -> dict: diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index d7698739c..5f7c0f288 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -38,7 +38,11 @@ from packaging.version import Version from typing_extensions import deprecated -from langfuse._client.attributes import LangfuseOtelSpanAttributes, _serialize +from langfuse._client.attributes import ( + LangfuseOtelSpanAttributes, + _flatten_and_serialize_metadata_values, + _serialize, +) from langfuse._client.constants import ( LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, ObservationTypeGenerationLike, @@ -2791,10 +2795,14 @@ async def _process_experiment_item( propagated_experiment_attributes = PropagatedExperimentAttributes( experiment_id=experiment_id, experiment_name=experiment_run_name, - experiment_metadata=_serialize(experiment_metadata), + experiment_metadata=_flatten_and_serialize_metadata_values( + experiment_metadata + ), experiment_dataset_id=dataset_id, experiment_item_id=experiment_item_id, - experiment_item_metadata=_serialize(item_metadata), + experiment_item_metadata=_flatten_and_serialize_metadata_values( + item_metadata if isinstance(item_metadata, dict) else None + ), experiment_item_root_observation_id=span.id, ) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 2de9cefb9..988f1f26e 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -68,10 +68,10 @@ class PropagatedExperimentAttributes(TypedDict): experiment_id: str experiment_name: str - experiment_metadata: Optional[str] + experiment_metadata: Optional[Dict[str, str]] experiment_dataset_id: Optional[str] experiment_item_id: str - experiment_item_metadata: Optional[str] + experiment_item_metadata: Optional[Dict[str, str]] experiment_item_root_observation_id: str @@ -247,9 +247,20 @@ def _propagate_attributes( "trace_name": trace_name, } - propagated_string_attributes = propagated_string_attributes | ( - cast(Dict[str, Union[str, List[str], None]], experiment) or {} - ) + propagated_metadata_attributes: Dict[str, Optional[Dict[str, str]]] = { + "metadata": metadata, + } + + if experiment: + for key, value in experiment.items(): + if key in ("experiment_metadata", "experiment_item_metadata"): + propagated_metadata_attributes[key] = cast( + Optional[Dict[str, str]], value + ) + else: + propagated_string_attributes[key] = cast( + Optional[Union[str, List[str]]], value + ) # Filter out None values propagated_string_attributes = { @@ -268,16 +279,19 @@ def _propagate_attributes( as_baggage=as_baggage, ) - if metadata is not None: + for metadata_key, metadata_value in propagated_metadata_attributes.items(): + if metadata_value is None: + continue + validated_metadata: Dict[str, str] = {} - for key, value in metadata.items(): - if _validate_string_value(value=value, key=f"metadata.{key}"): + for key, value in metadata_value.items(): + if _validate_string_value(value=value, key=f"{metadata_key}.{key}"): validated_metadata[key] = value if validated_metadata: context = _set_propagated_attribute( - key="metadata", + key=metadata_key, value=validated_metadata, context=context, span=current_span, @@ -322,9 +336,10 @@ def _get_propagated_attributes_from_context( if isinstance(value, dict): # Handle metadata + span_key = _get_propagated_span_key(key) + for k, v in value.items(): - span_key = f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.{k}" - propagated_attributes[span_key] = v + propagated_attributes[f"{span_key}.{k}"] = v else: span_key = _get_propagated_span_key(key) @@ -387,7 +402,7 @@ def _set_propagated_attribute( # Handle metadata for k, v in value.items(): span.set_attribute( - key=f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.{k}", + key=f"{span_key}.{k}", value=v, ) @@ -469,10 +484,14 @@ def _get_span_key_from_baggage_key(key: str) -> Optional[str]: # Remove prefix to get the actual key name suffix = key[len(LANGFUSE_BAGGAGE_PREFIX) :] - if suffix.startswith("metadata_"): - metadata_key = suffix[len("metadata_") :] + for metadata_key in ("metadata", "experiment_metadata", "experiment_item_metadata"): + baggage_metadata_prefix = f"{metadata_key}_" - return _get_propagated_span_key(metadata_key) + if suffix.startswith(baggage_metadata_prefix): + return ( + f"{_get_propagated_span_key(metadata_key)}." + f"{suffix[len(baggage_metadata_prefix) :]}" + ) return _get_propagated_span_key(suffix) @@ -484,6 +503,7 @@ def _get_propagated_span_key(key: str) -> str: "version": LangfuseOtelSpanAttributes.VERSION, "tags": LangfuseOtelSpanAttributes.TRACE_TAGS, "trace_name": LangfuseOtelSpanAttributes.TRACE_NAME, + "metadata": LangfuseOtelSpanAttributes.TRACE_METADATA, "experiment_id": LangfuseOtelSpanAttributes.EXPERIMENT_ID, "experiment_name": LangfuseOtelSpanAttributes.EXPERIMENT_NAME, "experiment_metadata": LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, diff --git a/tests/e2e/test_experiments.py b/tests/e2e/test_experiments.py index 5f18d9c0b..80881f144 100644 --- a/tests/e2e/test_experiments.py +++ b/tests/e2e/test_experiments.py @@ -4,8 +4,10 @@ from typing import Any, Dict, List import pytest +from opentelemetry import trace as otel_trace_api from langfuse import get_client +from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse.experiment import ( Evaluation, ExperimentData, @@ -135,6 +137,63 @@ def test_run_experiment_on_local_dataset(sample_dataset): ) +def test_run_experiment_flattens_large_metadata_for_server_ingestion(): + """Server ingestion handles flattened experiment metadata on non-SDK child spans.""" + langfuse_client = get_client() + external_tracer = otel_trace_api.get_tracer("ai.langfuse-python.e2e") + external_span_name = "external-experiment-metadata-child-" + create_uuid()[:8] + + experiment_metadata = { + "mode": "offline", + "job_name": "agent-eval/PR-4", + "build_url": "https://example.com/job/agent-eval-example/job/PR-4", + "agent_name": "agent-eval-example", + } + + def task_with_external_child(*, item: ExperimentItem, **kwargs: Dict[str, Any]): + with external_tracer.start_as_current_span(external_span_name) as span: + span.set_attribute("gen_ai.operation.name", "experiment-metadata-e2e") + + return "processed" + + result = langfuse_client.run_experiment( + name="Flattened Experiment Metadata " + create_uuid()[:8], + data=[{"input": "test input", "expected_output": "processed"}], + task=task_with_external_child, + metadata=experiment_metadata, + ) + + langfuse_client.flush() + + trace_id = result.item_results[0].trace_id + assert trace_id is not None + + trace = wait_for_trace( + trace_id, + is_result_ready=lambda fetched_trace: any( + observation.name == external_span_name + for observation in fetched_trace.observations + ), + ) + + assert trace.metadata is not None + for metadata_key, metadata_value in experiment_metadata.items(): + assert trace.metadata[metadata_key] == metadata_value + + external_observation = next( + observation + for observation in trace.observations + if observation.name == external_span_name + ) + external_metadata = external_observation.metadata or {} + + assert not any( + key == LangfuseOtelSpanAttributes.EXPERIMENT_METADATA + or key.startswith(f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.") + for key in external_metadata + ) + + def test_run_experiment_on_langfuse_dataset(): """Test running experiment on Langfuse dataset.""" langfuse_client = get_client() diff --git a/tests/unit/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py index 67cd703c3..e1085b879 100644 --- a/tests/unit/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -2394,15 +2394,22 @@ def task_with_child_spans(*, item, **kwargs): LangfuseOtelSpanAttributes.EXPERIMENT_NAME, result.run_name, ) + for metadata_key, metadata_value in experiment_metadata.items(): + self.verify_span_attribute( + first_root, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.{metadata_key}", + metadata_value, + ) + self.verify_span_attribute( first_root, - LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, - _serialize(experiment_metadata), + f"{LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA}.item_type", + "test", ) self.verify_span_attribute( first_root, - LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, - _serialize({"item_type": "test", "priority": "high"}), + f"{LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA}.priority", + "high", ) # Environment should be set to sdk-experiment @@ -2437,11 +2444,12 @@ def task_with_child_spans(*, item, **kwargs): LangfuseOtelSpanAttributes.EXPERIMENT_NAME, result.run_name, ) - self.verify_span_attribute( - child_span, - LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, - _serialize(experiment_metadata), - ) + for metadata_key, metadata_value in experiment_metadata.items(): + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.{metadata_key}", + metadata_value, + ) self.verify_span_attribute( child_span, LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_ID, @@ -2622,11 +2630,12 @@ def task_with_children(*, item, **kwargs): ) # Should have experiment metadata - self.verify_span_attribute( - first_root, - LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, - _serialize(experiment_metadata), - ) + for metadata_key, metadata_value in experiment_metadata.items(): + self.verify_span_attribute( + first_root, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.{metadata_key}", + metadata_value, + ) # Environment should be set to sdk-experiment self.verify_span_attribute( @@ -2658,17 +2667,23 @@ def task_with_children(*, item, **kwargs): ) # Experiment metadata should be propagated + for metadata_key, metadata_value in experiment_metadata.items(): + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.{metadata_key}", + metadata_value, + ) + + # Item metadata should be propagated self.verify_span_attribute( child_span, - LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, - _serialize(experiment_metadata), + f"{LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA}.source", + "dataset", ) - - # Item metadata should be propagated self.verify_span_attribute( child_span, - LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, - _serialize({"source": "dataset", "index": 0}), + f"{LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA}.index", + "0", ) # Environment should be propagated to children @@ -2821,19 +2836,21 @@ def task_with_child(*, item, **kwargs): # Verify child span has both experiment and item metadata propagated child_span = self.get_span_by_name(memory_exporter, "metadata-child") - # Verify experiment metadata is serialized and propagated - self.verify_span_attribute( - child_span, - LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, - _serialize(experiment_metadata), - ) + # Verify experiment metadata is flattened and propagated + for metadata_key, metadata_value in experiment_metadata.items(): + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.{metadata_key}", + _serialize(metadata_value), + ) - # Verify item metadata is serialized and propagated - self.verify_span_attribute( - child_span, - LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA, - _serialize(item_metadata), - ) + # Verify item metadata is flattened and propagated + for metadata_key, metadata_value in item_metadata.items(): + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_ITEM_METADATA}.{metadata_key}", + _serialize(metadata_value), + ) # Verify environment is propagated to child self.verify_span_attribute( @@ -2842,6 +2859,50 @@ def task_with_child(*, item, **kwargs): LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT, ) + def test_experiment_metadata_values_are_validated_individually( + self, langfuse_client, memory_exporter, caplog + ): + """Experiment metadata is flattened so large combined dicts still propagate.""" + + caplog.set_level("WARNING", logger="langfuse") + + experiment_metadata = { + "job_name": "j" * 150, + "build_url": "b" * 150, + "mode": "offline", + } + + local_data = [{"input": "test", "expected_output": "success"}] + + def task_with_child(*, item, **kwargs): + child = langfuse_client.start_observation(name="large-metadata-child") + child.end() + return "result" + + langfuse_client.run_experiment( + name="Large Metadata Test", + data=local_data, + task=task_with_child, + metadata=experiment_metadata, + ) + + langfuse_client.flush() + + child_span = self.get_span_by_name(memory_exporter, "large-metadata-child") + + for metadata_key, metadata_value in experiment_metadata.items(): + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.EXPERIMENT_METADATA}.{metadata_key}", + metadata_value, + ) + + self.verify_missing_attribute( + child_span, + LangfuseOtelSpanAttributes.EXPERIMENT_METADATA, + ) + assert "experiment_metadata' value is over 200 characters" not in caplog.text + class TestPropagateAttributesTraceName(TestPropagateAttributesBase): """Tests for trace_name parameter propagation.""" From 44125670283a1fecbb0fe7e6c1c6e8b20015915b Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 24 Apr 2026 15:21:40 +0000 Subject: [PATCH 265/296] chore: release v4.5.1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c88ea61b5..de3d1de9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.5.0" +version = "4.5.1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 6c8616dfa..fc9acf6d6 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-14T11:30:30.93971086Z" +exclude-newer = "2026-04-17T15:21:35.979946599Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.5.0" +version = "4.5.1" source = { editable = "." } dependencies = [ { name = "backoff" }, From 5ef17a05d5e6d5f81ed9f7337f363afa69f69d26 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:01:19 +0900 Subject: [PATCH 266/296] chore: improve agent development setup (#1642) --- .codex/environments/environment.toml | 5 ++ .github/PULL_REQUEST_TEMPLATE.md | 30 ++++++++ .github/workflows/validate-pr-title.yml | 45 ++++++++++++ .gitignore | 5 ++ AGENTS.md | 24 ++++++- CONTRIBUTING.md | 96 +++++++++++++++++++------ code_review.md | 38 ++++++++++ scripts/codex/maintenance.sh | 8 +++ scripts/codex/quick-check.sh | 9 +++ scripts/codex/setup.sh | 13 ++++ 10 files changed, 249 insertions(+), 24 deletions(-) create mode 100644 .codex/environments/environment.toml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/validate-pr-title.yml create mode 100644 code_review.md create mode 100755 scripts/codex/maintenance.sh create mode 100755 scripts/codex/quick-check.sh create mode 100755 scripts/codex/setup.sh diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml new file mode 100644 index 000000000..a5a97658e --- /dev/null +++ b/.codex/environments/environment.toml @@ -0,0 +1,5 @@ +version = 1 +name = "langfuse-python" + +[setup] +script = "bash scripts/codex/setup.sh" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..a06986558 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +## What does this PR do? + +> PR title must follow Conventional Commits, for example `feat: add dataset scoring helper` or `fix(openai): preserve trace context`. + +Fixes # + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Refactor +- [ ] Documentation update +- [ ] Tooling, CI, or repo maintenance + +## Verification + +List the main commands you ran: + +```bash + +``` + +## Checklist + +- [ ] I self-reviewed the diff using `code_review.md`. +- [ ] I added or updated tests for behavior changes. +- [ ] I updated docs, examples, or `.env.template` if needed. +- [ ] I did not hand-edit generated files; if generated files changed, I used the upstream regeneration path. +- [ ] I did not commit secrets or credentials. diff --git a/.github/workflows/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml new file mode 100644 index 000000000..1d94b4f6b --- /dev/null +++ b/.github/workflows/validate-pr-title.yml @@ -0,0 +1,45 @@ +--- +name: "Validate PR Title" + +on: + pull_request: + branches: + - "**" + types: + - opened + - edited + - synchronize + - reopened + +permissions: {} + +jobs: + validate-pr-title: + runs-on: ubuntu-latest + permissions: + statuses: write + pull-requests: read + steps: + - name: Validate PR title follows conventional commits + uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + security + requireScope: false + validateSingleCommit: false + ignoreLabels: | + bot + ignore-semantic-pull-request diff --git a/.gitignore b/.gitignore index bebafdd05..bfd138387 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,8 @@ docs tests/mocks/llama-index-storage *.local.* + +# Codex local runtime state +.codex/log/ +.codex/sessions/ +.codex/tmp/ diff --git a/AGENTS.md b/AGENTS.md index eff827d76..a1a6f5846 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,7 @@ This repository contains the Langfuse Python SDK. - `tests/live_provider/`: live OpenAI / LangChain provider tests - `tests/support/`: shared helpers for e2e tests - `scripts/select_e2e_shard.py`: CI shard selector for `tests/e2e` +- `scripts/codex/`: Codex cloud/worktree bootstrap and shared quick checks ## Working Style @@ -34,6 +35,8 @@ This repository contains the Langfuse Python SDK. - Keep repo-shared instructions here. Keep personal or machine-specific notes out of version control. - Keep tests independent and parallel-safe by default. - For bug fixes, prefer writing or identifying the failing test first, confirm the failure, then implement the fix. +- For complex or ambiguous tasks, plan first, identify the likely verification path, then implement. +- Before final handoff, review the diff for correctness, regressions, missing tests, and accidental generated-file edits. ## Setup And Quality Commands @@ -43,6 +46,7 @@ uv run pre-commit install uv run --frozen ruff check . uv run --frozen ruff format . uv run --frozen mypy langfuse --no-error-summary +bash scripts/codex/quick-check.sh ``` ## Test Commands @@ -66,6 +70,18 @@ uv run --frozen pytest -n 4 --dist worksteal tests/live_provider -m "live_provid uv run --frozen pytest tests/unit/test_resource_manager.py::test_pause_signals_score_consumer_shutdown ``` +Minimum verification matrix: + +| Change scope | Minimum verification | +| --- | --- | +| Docs or comments only | `uv run --frozen ruff format --check .` if Python files changed | +| Python source only | `uv run --frozen ruff check .` + `uv run --frozen mypy langfuse --no-error-summary` + targeted unit tests | +| Unit-test-only change | targeted `uv run --frozen pytest ...` for the changed tests | +| Shutdown, flushing, worker-thread, or OTEL-heavy change | targeted resource-manager/OTEL tests plus affected integration tests when relevant | +| OpenAI or LangChain instrumentation | targeted unit tests using exporter-local assertions; add e2e/live-provider coverage only when unit tests cannot cover behavior | +| Generated API client or public API contract | upstream Fern/OpenAPI regeneration path plus targeted SDK serialization/deserialization tests | +| CI, sharding, or bootstrap | relevant script test plus CI workflow review against this file's CI contract | + ## Test Topology ### `tests/unit` @@ -96,6 +112,7 @@ The main CI workflow currently runs: - `tests/unit` on a Python 3.10-3.14 matrix - `tests/e2e` in 2 mechanical shards plus a serial subset inside each shard - `tests/live_provider` as one always-on suite +- PR title validation for Conventional Commits If you change the e2e split: @@ -113,6 +130,7 @@ If you change CI bootstrap: - Keep changes scoped. Avoid unrelated refactors. - Prefer `LANGFUSE_BASE_URL`; `LANGFUSE_HOST` is deprecated and is only kept for compatibility tests. - If you touch `langfuse/api/`, regenerate it from the upstream Fern/OpenAPI source instead of hand-editing files. +- If you change public SDK behavior, update examples, README snippets, or generated reference docs when they would otherwise become stale. - If you touch shutdown, flushing, or worker-thread behavior, run the relevant resource-manager and OTEL-heavy tests. - If you change OpenAI or LangChain instrumentation, keep as much coverage as possible in `tests/unit` using exporter-local assertions, and leave only the minimal necessary coverage in `tests/e2e` / `tests/live_provider`. - Never commit secrets or credentials. @@ -120,9 +138,11 @@ If you change CI bootstrap: ## Commit And PR Rules -- Commit messages and PR titles should follow Conventional Commits: `type(scope): description` or `type: description`. +- Commit messages and PR titles must follow Conventional Commits: `type(scope): description` or `type: description`. +- Allowed common types include `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, and `security`. - Keep commits focused and atomic. -- In PR descriptions, list the main verification commands you ran. +- Before opening a PR, self-review the diff and check `code_review.md` for the repo-specific review checklist. +- In PR descriptions, list the main verification commands you ran and call out any skipped checks with the reason. ## Python-Specific Notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 946400a5d..45f1ed55d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,48 +4,100 @@ ### Install dependencies -``` -uv sync +```bash +uv sync --locked ``` -### Add Pre-commit +### Add pre-commit -``` +```bash uv run pre-commit install ``` -### Type Checking +### Quality checks -To run type checking on the langfuse package, run: -```sh -uv run mypy langfuse --no-error-summary +```bash +uv run --frozen ruff check . +uv run --frozen ruff format . +uv run --frozen mypy langfuse --no-error-summary +``` + +For a broad local confidence check, run: + +```bash +bash scripts/codex/quick-check.sh ``` ### Tests -#### Setup +Unit tests do not require a running Langfuse server: -- Add .env based on .env.template +```bash +uv run --frozen pytest -n auto --dist worksteal tests/unit +``` -#### Run +E2E tests require a running Langfuse server and environment variables based on `.env.template`: -- Run all +```bash +uv run --frozen pytest -n 4 --dist worksteal tests/e2e -m "not serial_e2e" +uv run --frozen pytest tests/e2e -m "serial_e2e" +``` + +Live-provider tests make real provider calls and require provider API keys: + +```bash +uv run --frozen pytest -n 4 --dist worksteal tests/live_provider -m "live_provider" +``` + +Run a specific test with: - ``` - uv run --env-file .env pytest -s -v --log-cli-level=INFO - ``` +```bash +uv run --frozen pytest tests/unit/test_resource_manager.py::test_pause_signals_score_consumer_shutdown +``` + +## Codex Cloud Setup + +This repository includes repo-owned Codex setup so agents can start from a reproducible environment. + +Recommended Codex UI configuration: + +1. Create a Codex cloud environment for this repository. +2. Set the setup script to: + + ```bash + bash scripts/codex/setup.sh + ``` + +3. Set the maintenance script to: + + ```bash + bash scripts/codex/maintenance.sh + ``` + +4. Keep agent internet access disabled by default, or allow only the domains required for the task. +5. Add secrets and environment variables in the Codex UI instead of committing them. + +## Pull Requests + +PR titles and commit messages must follow Conventional Commits: + +```text +type(scope): description +type: description +``` -- Run a specific test +Common types include `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, and `security`. - ``` - uv run --env-file .env pytest -s -v --log-cli-level=INFO tests/test_core_sdk.py::test_flush - ``` +Before opening a PR: -- E2E tests involving OpenAI and Serp API are usually skipped, remove skip decorators in [tests/test_langchain.py](tests/test_langchain.py) to run them. +- Self-review the diff and use `code_review.md` for the repo-specific checklist. +- Keep changes focused and avoid unrelated refactors. +- Add or update tests for behavior changes. +- List the verification commands you ran in the PR description. -### Update openapi spec +### Update OpenAPI spec -A PR with the changes is automatically created upon changing the Spec in the langfuse repo. +The generated API client in `langfuse/api/` must not be hand-edited. Regenerate it from the upstream Fern/OpenAPI source. ### Publish release diff --git a/code_review.md b/code_review.md new file mode 100644 index 000000000..a3c55051c --- /dev/null +++ b/code_review.md @@ -0,0 +1,38 @@ +# Langfuse Python SDK Review Checklist + +Use this checklist for `/review`, PR review, or self-review before handoff. + +## Priorities + +- Findings first: correctness bugs, regressions, security/privacy risks, performance issues with real impact, and missing tests for risky behavior. +- Keep line references tight and actionable. +- If no findings, say so explicitly and mention any residual risk or unrun verification. + +## SDK Correctness + +- Public SDK behavior should remain backwards compatible unless the PR is explicitly breaking. +- Prefer `LANGFUSE_BASE_URL`; `LANGFUSE_HOST` is deprecated and should only appear in compatibility paths or tests. +- Check shutdown, flushing, background task, and resource-manager changes for races, dropped events/scores/media, daemon-thread leaks, and hanging interpreter shutdown. +- OpenTelemetry changes should preserve context propagation, span parenting, exporter-local testability, and idempotent instrumentation setup. +- OpenAI and LangChain instrumentation should avoid brittle assertions on provider internals; prefer stable exporter-local behavior in unit tests. + +## API And Generated Code + +- Do not hand-edit `langfuse/api/`; regenerate it from the upstream Fern/OpenAPI source. +- Public API or serialization changes should include tests for request shape, response shape, and backwards-compatible aliases when relevant. +- Update README examples, `.env.template`, or generated reference docs when changed behavior would make them stale. + +## Tests And CI + +- Unit tests must not require a running Langfuse server. +- E2E tests should use bounded polling helpers from `tests/support/`, not raw `sleep()`. +- New e2e files must be named `tests/e2e/test_*.py` so mechanical CI sharding includes them. +- Use `serial_e2e` only for tests that are unsafe with shared-server concurrency. +- Live-provider tests should assert stable provider-facing behavior, not exact observation counts unless counts are the behavior under test. + +## Python Style + +- Exception messages should not inline f-string literals in `raise` statements; build the message in a variable first. +- Keep edits ASCII-only unless the file already uses Unicode or Unicode is clearly required. +- Keep changes scoped; avoid opportunistic refactors. +- Never commit secrets or credentials. diff --git a/scripts/codex/maintenance.sh b/scripts/codex/maintenance.sh new file mode 100755 index 000000000..aba80e6a3 --- /dev/null +++ b/scripts/codex/maintenance.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$repo_root" + +uv sync --locked +uv cache prune --ci >/dev/null 2>&1 || true diff --git a/scripts/codex/quick-check.sh b/scripts/codex/quick-check.sh new file mode 100755 index 000000000..fd3c1823c --- /dev/null +++ b/scripts/codex/quick-check.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$repo_root" + +uv run --frozen ruff check . +uv run --frozen mypy langfuse --no-error-summary +uv run --frozen pytest -n auto --dist worksteal tests/unit diff --git a/scripts/codex/setup.sh b/scripts/codex/setup.sh new file mode 100755 index 000000000..b6e7d30f5 --- /dev/null +++ b/scripts/codex/setup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$repo_root" + +if ! command -v uv >/dev/null 2>&1; then + python3 -m pip install --user "uv==0.11.2" + export PATH="$HOME/.local/bin:$PATH" +fi + +uv sync --locked +uv run --frozen python --version From 3166cb8bd6667e4a49ab11e3f049719723c88552 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 16:32:10 +0900 Subject: [PATCH 267/296] feat(ci): add RunnerContext and RegressionError for experiment GH action (#1635) * feat(ci): add RunnerContext and RegressionError for experiment GH action Adds the SDK-side primitives consumed by the upcoming `langfuse/experiment-action` GitHub Action (LFE-9241): - `RunnerContext` wraps `Langfuse.run_experiment` with action-injected defaults (data, dataset_version, name, run_name, metadata). Users can override any default on the call site; metadata is merged with user-supplied keys winning on collision. - `RegressionError` lets users signal a CI gate failure and optionally pass structured `metric`/`value`/`threshold` fields so the action can render a callout in the PR comment. Both live in a dedicated `langfuse/ci.py` module so the CI surface stays isolated from the general experiment API. Co-Authored-By: Claude Opus 4.7 (1M context) * refactor(experiment): move RunnerContext and RegressionError into experiment module Relocates the CI-action primitives from the standalone `langfuse/ci.py` module into `langfuse/experiment.py` alongside the other experiment types. Deletes `langfuse/ci.py` and renames the tests accordingly. The public import paths (`from langfuse import RunnerContext, RegressionError`) are unchanged. `CompositeEvaluatorFunction` is imported under `TYPE_CHECKING` to avoid a circular import with `langfuse.batch_evaluation`. The signature-drift guard now resolves the forward reference via `typing.get_type_hints(..., localns=...)`. Co-Authored-By: Claude Opus 4.7 (1M context) * test: rename test_runner_context.py to test_experiment.py Mirrors the module name now that RunnerContext and RegressionError live in `langfuse.experiment`. Co-Authored-By: Claude Opus 4.7 (1M context) * feat(experiment): tighten RunnerContext + RegressionError public surface - RunnerContext no longer carries `name` or `run_name` as context-level defaults. `name` is now required on every `run_experiment` call (supports the action's directory-of-experiments mode where each script must name itself). `run_name` passes straight through to `Langfuse.run_experiment`. - RegressionError gains three typed `@overload` signatures (minimal, free-form message, structured metric/value/threshold) so type checkers enforce that `metric` and `value` are supplied together. At runtime, partial structured input falls back to the default message instead of rendering misleading `None` placeholders in PR comments. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- langfuse/__init__.py | 4 +- langfuse/experiment.py | 156 +++++++++++++++++++++ tests/unit/test_experiment.py | 248 ++++++++++++++++++++++++++++++++++ 3 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_experiment.py diff --git a/langfuse/__init__.py b/langfuse/__init__.py index d33febca7..08d8325cf 100644 --- a/langfuse/__init__.py +++ b/langfuse/__init__.py @@ -8,7 +8,7 @@ EvaluatorStats, MapperFunction, ) -from langfuse.experiment import Evaluation +from langfuse.experiment import Evaluation, RegressionError, RunnerContext from ._client import client as _client_module from ._client.attributes import LangfuseOtelSpanAttributes @@ -63,6 +63,8 @@ "EvaluatorStats", "BatchEvaluationResumeToken", "BatchEvaluationResult", + "RunnerContext", + "RegressionError", "__version__", "is_default_export_span", "is_langfuse_span", diff --git a/langfuse/experiment.py b/langfuse/experiment.py index 67b50a900..404c96e1d 100644 --- a/langfuse/experiment.py +++ b/langfuse/experiment.py @@ -6,7 +6,9 @@ """ import asyncio +from datetime import datetime from typing import ( + TYPE_CHECKING, Any, Awaitable, Dict, @@ -15,12 +17,17 @@ Protocol, TypedDict, Union, + overload, ) from langfuse.api import DatasetItem from langfuse.logger import langfuse_logger as logger from langfuse.types import ExperimentScoreType +if TYPE_CHECKING: + from langfuse._client.client import Langfuse + from langfuse.batch_evaluation import CompositeEvaluatorFunction + class LocalExperimentItem(TypedDict, total=False): """Structure for local experiment data items (not from Langfuse datasets). @@ -1049,3 +1056,152 @@ def langfuse_evaluator( ) return langfuse_evaluator + + +class RunnerContext: + """Wraps :meth:`Langfuse.run_experiment` with CI-injected defaults. + + Intended for use with the ``langfuse/experiment-action`` GitHub Action + (https://github.com/langfuse/experiment-action). The action builds a + ``RunnerContext`` before invoking the user's ``experiment(context)`` + function. Defaults set here (dataset, metadata tags) are applied when + the user omits them on the :meth:`run_experiment` call; users can + override any default by passing the corresponding argument explicitly. + """ + + def __init__( + self, + *, + client: "Langfuse", + data: Optional[ExperimentData] = None, + dataset_version: Optional[datetime] = None, + metadata: Optional[Dict[str, str]] = None, + ): + """Build a ``RunnerContext`` populated with defaults for ``run_experiment``. + + Typically called by the ``langfuse/experiment-action`` GitHub Action, + not by end users directly. Every field except ``client`` is optional: + fields left as ``None`` simply mean the corresponding argument must be + supplied on the :meth:`run_experiment` call. + + Args: + client: Initialized Langfuse SDK client used to execute the + experiment. The action creates this from the + ``langfuse_public_key`` / ``langfuse_secret_key`` / + ``langfuse_base_url`` inputs. + data: Default dataset items to run the experiment on. Accepts + either ``List[LocalExperimentItem]`` or ``List[DatasetItem]``. + Injected by the action when ``dataset_name`` is configured. + If ``None``, the user must pass ``data=`` to + :meth:`run_experiment`. + dataset_version: Optional pinned dataset version. Injected by the + action when ``dataset_version`` is configured. + metadata: Default metadata attached to every experiment trace and + the dataset run. The action injects GitHub-sourced tags (SHA, + PR link, workflow run link, branch, GH user, etc.). Merged + with any ``metadata`` passed to :meth:`run_experiment`, with + user-supplied keys winning on collision. + """ + self.client = client + self.data = data + self.dataset_version = dataset_version + self.metadata = metadata + + def run_experiment( + self, + *, + name: str, + run_name: Optional[str] = None, + description: Optional[str] = None, + data: Optional[ExperimentData] = None, + task: TaskFunction, + evaluators: List[EvaluatorFunction] = [], + composite_evaluator: Optional["CompositeEvaluatorFunction"] = None, + run_evaluators: List[RunEvaluatorFunction] = [], + max_concurrency: int = 50, + metadata: Optional[Dict[str, str]] = None, + _dataset_version: Optional[datetime] = None, + ) -> ExperimentResult: + resolved_data = data if data is not None else self.data + if resolved_data is None: + raise ValueError( + "`data` must be provided either on the RunnerContext or the run_experiment call" + ) + + resolved_dataset_version = ( + _dataset_version if _dataset_version is not None else self.dataset_version + ) + + merged_metadata: Optional[Dict[str, str]] + if self.metadata is None and metadata is None: + merged_metadata = None + else: + merged_metadata = {**(self.metadata or {}), **(metadata or {})} + + return self.client.run_experiment( + name=name, + run_name=run_name, + description=description, + data=resolved_data, + task=task, + evaluators=evaluators, + composite_evaluator=composite_evaluator, + run_evaluators=run_evaluators, + max_concurrency=max_concurrency, + metadata=merged_metadata, + _dataset_version=resolved_dataset_version, + ) + + +class RegressionError(Exception): + """Raised by a user's ``experiment`` function to signal a CI gate failure. + + Intended for use with the ``langfuse/experiment-action`` GitHub Action + (https://github.com/langfuse/experiment-action). The action catches this + exception and, when ``should_fail_on_error`` is enabled, fails the + workflow run and renders a callout in the PR comment using + ``metric``/``value``/``threshold`` if supplied, otherwise ``str(exc)``. + + Callers choose one of three forms: + + - ``RegressionError(result=r)`` — minimal, generic message. + - ``RegressionError(result=r, message="...")`` — free-form message. + - ``RegressionError(result=r, metric="acc", value=0.7, threshold=0.9)`` — + structured; ``metric`` and ``value`` must be provided together so the + action can render a targeted callout without ``None`` placeholders. + """ + + @overload + def __init__(self, *, result: ExperimentResult) -> None: ... + @overload + def __init__(self, *, result: ExperimentResult, message: str) -> None: ... + @overload + def __init__( + self, + *, + result: ExperimentResult, + metric: str, + value: float, + threshold: Optional[float] = None, + message: Optional[str] = None, + ) -> None: ... + def __init__( + self, + *, + result: ExperimentResult, + metric: Optional[str] = None, + value: Optional[float] = None, + threshold: Optional[float] = None, + message: Optional[str] = None, + ): + self.result = result + self.metric = metric + self.value = value + self.threshold = threshold + if message is not None: + formatted = message + elif metric is not None and value is not None: + formatted = f"Regression on `{metric}`: {value} (threshold {threshold})" + else: + formatted = "Experiment regression detected" + super().__init__(formatted) diff --git a/tests/unit/test_experiment.py b/tests/unit/test_experiment.py new file mode 100644 index 000000000..c6c8465a3 --- /dev/null +++ b/tests/unit/test_experiment.py @@ -0,0 +1,248 @@ +"""Tests for ``langfuse.experiment`` — ``RunnerContext`` and ``RegressionError``.""" + +import inspect +import typing +from datetime import datetime +from typing import get_type_hints +from unittest.mock import MagicMock + +import pytest + +from langfuse import RegressionError, RunnerContext +from langfuse._client.client import Langfuse +from langfuse.batch_evaluation import CompositeEvaluatorFunction + + +def _noop_task(*, item, **kwargs): # pragma: no cover - never invoked via mock + return None + + +def _make_ctx(**kwargs) -> RunnerContext: + client = MagicMock(spec=Langfuse) + client.run_experiment.return_value = "result-sentinel" + return RunnerContext(client=client, **kwargs) + + +class TestRunnerContextDefaults: + def test_context_defaults_flow_through(self): + ctx_data = [{"input": "a"}] + ctx_version = datetime(2026, 1, 1) + ctx = _make_ctx( + data=ctx_data, + dataset_version=ctx_version, + metadata={"sha": "abc123"}, + ) + + result = ctx.run_experiment(name="exp", task=_noop_task) + + assert result == "result-sentinel" + ctx.client.run_experiment.assert_called_once() + kwargs = ctx.client.run_experiment.call_args.kwargs + assert kwargs["name"] == "exp" + assert kwargs["data"] is ctx_data + assert kwargs["metadata"] == {"sha": "abc123"} + assert kwargs["_dataset_version"] == ctx_version + assert kwargs["task"] is _noop_task + + def test_call_overrides_win(self): + ctx = _make_ctx( + data=[{"input": "ctx"}], + dataset_version=datetime(2026, 1, 1), + ) + + override_data = [{"input": "override"}] + override_version = datetime(2026, 6, 6) + ctx.run_experiment( + name="exp", + task=_noop_task, + run_name="call-run", + data=override_data, + _dataset_version=override_version, + ) + + kwargs = ctx.client.run_experiment.call_args.kwargs + assert kwargs["name"] == "exp" + assert kwargs["run_name"] == "call-run" + assert kwargs["data"] is override_data + assert kwargs["_dataset_version"] == override_version + + +class TestRunnerContextMetadataMerge: + def test_user_keys_win_on_collision(self): + ctx = _make_ctx( + data=[{"input": "a"}], + metadata={"sha": "abc", "branch": "main"}, + ) + ctx.run_experiment( + name="exp", task=_noop_task, metadata={"sha": "def", "pr": "42"} + ) + assert ctx.client.run_experiment.call_args.kwargs["metadata"] == { + "sha": "def", + "branch": "main", + "pr": "42", + } + + def test_context_metadata_only(self): + ctx = _make_ctx(data=[{"input": "a"}], metadata={"sha": "abc"}) + ctx.run_experiment(name="exp", task=_noop_task) + assert ctx.client.run_experiment.call_args.kwargs["metadata"] == {"sha": "abc"} + + def test_call_metadata_only(self): + ctx = _make_ctx(data=[{"input": "a"}]) + ctx.run_experiment(name="exp", task=_noop_task, metadata={"pr": "1"}) + assert ctx.client.run_experiment.call_args.kwargs["metadata"] == {"pr": "1"} + + def test_both_none_stays_none(self): + ctx = _make_ctx(data=[{"input": "a"}]) + ctx.run_experiment(name="exp", task=_noop_task) + assert ctx.client.run_experiment.call_args.kwargs["metadata"] is None + + +class TestRunnerContextLocalItems: + def test_local_items_pass_through_as_context_default(self): + items = [{"input": "x", "expected_output": "y"}] + ctx = _make_ctx(data=items) + ctx.run_experiment(name="exp", task=_noop_task) + assert ctx.client.run_experiment.call_args.kwargs["data"] is items + + def test_local_items_pass_through_as_call_override(self): + ctx = _make_ctx() + items = [{"input": "x"}] + ctx.run_experiment(name="exp", task=_noop_task, data=items) + assert ctx.client.run_experiment.call_args.kwargs["data"] is items + + +class TestRunnerContextValidation: + def test_missing_data_raises(self): + ctx = _make_ctx() + with pytest.raises(ValueError, match="data"): + ctx.run_experiment(name="exp", task=_noop_task) + + +class TestRegressionError: + def test_is_exception(self): + result = MagicMock() + exc = RegressionError(result=result) + assert isinstance(exc, Exception) + assert exc.result is result + + def test_default_message(self): + exc = RegressionError(result=MagicMock()) + assert str(exc) == "Experiment regression detected" + assert exc.metric is None + assert exc.value is None + assert exc.threshold is None + + def test_structured_message(self): + exc = RegressionError( + result=MagicMock(), metric="avg_accuracy", value=0.78, threshold=0.9 + ) + assert exc.metric == "avg_accuracy" + assert exc.value == 0.78 + assert exc.threshold == 0.9 + assert "avg_accuracy" in str(exc) + assert "0.78" in str(exc) + assert "0.9" in str(exc) + + def test_free_form_message(self): + exc = RegressionError( + result=MagicMock(), + message="custom explanation", + ) + assert str(exc) == "custom explanation" + + def test_message_wins_over_structured(self): + exc = RegressionError( + result=MagicMock(), + metric="avg_accuracy", + value=0.5, + threshold=0.9, + message="custom explanation", + ) + assert str(exc) == "custom explanation" + assert exc.metric == "avg_accuracy" + assert exc.value == 0.5 + assert exc.threshold == 0.9 + + def test_partial_structured_falls_back_to_default(self): + """The structured overload requires ``metric`` and ``value`` together. + + If a caller bypasses the type checker and passes only one, we fall + back to the default message rather than rendering misleading + ``None`` placeholders in the PR comment. + """ + exc = RegressionError(result=MagicMock(), metric="avg_accuracy") # type: ignore[call-overload] + assert str(exc) == "Experiment regression detected" + + +class TestSignatureDriftGuard: + """Fails loudly if ``Langfuse.run_experiment`` grows a parameter that is + not threaded through ``RunnerContext.run_experiment``. + + ``data`` is the only genuinely relaxed parameter: it is required on the + client but optional on the RunnerContext so the action can inject it. + ``run_name`` and ``_dataset_version`` are already ``Optional`` on the + client and must match as-is. ``name`` is required on both — the action + supports a directory of experiments, so each script must name itself. + """ + + RELAXED_PARAMS = {"data"} + + # `CompositeEvaluatorFunction` is only imported under TYPE_CHECKING in + # ``langfuse.experiment`` to break the circular dependency with + # ``langfuse.batch_evaluation``, so its forward-ref must be resolved + # explicitly when inspecting annotations. + LOCALNS = {"CompositeEvaluatorFunction": CompositeEvaluatorFunction} + + def test_no_divergence(self): + client_param_names = self._param_names(Langfuse.run_experiment) + ctx_param_names = self._param_names(RunnerContext.run_experiment) + + assert client_param_names == ctx_param_names, ( + "RunnerContext.run_experiment params do not match " + "Langfuse.run_experiment. Missing: " + f"{client_param_names - ctx_param_names}. " + f"Extra: {ctx_param_names - client_param_names}." + ) + + client_hints = get_type_hints(Langfuse.run_experiment) + ctx_hints = get_type_hints( + RunnerContext.run_experiment, localns=self.LOCALNS + ) + + for name in client_param_names: + client_ann = client_hints.get(name, inspect.Parameter.empty) + ctx_ann = ctx_hints.get(name, inspect.Parameter.empty) + + if name in self.RELAXED_PARAMS: + # RunnerContext version must be Optional[]. + # Already-optional client annotations (``run_name``, + # ``_dataset_version``) just need to match as-is. + if self._is_optional(client_ann): + assert ctx_ann == client_ann, ( + f"param `{name}`: expected {client_ann}, got {ctx_ann}" + ) + else: + assert ctx_ann == typing.Optional[client_ann], ( + f"param `{name}`: expected Optional[{client_ann}], " + f"got {ctx_ann}" + ) + else: + assert ctx_ann == client_ann, ( + f"param `{name}`: annotation drift — " + f"client={client_ann}, context={ctx_ann}" + ) + + @staticmethod + def _param_names(func) -> set: + return { + name + for name in inspect.signature(func).parameters + if name != "self" + } + + @staticmethod + def _is_optional(annotation) -> bool: + origin = typing.get_origin(annotation) + args = typing.get_args(annotation) + return origin is typing.Union and type(None) in args From 560fbe477db1dec5f623ba1036dafd4d0bd3975e Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Mon, 4 May 2026 07:52:39 +0000 Subject: [PATCH 268/296] chore: release v4.6.0b1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de3d1de9e..99c704c0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.5.1" +version = "4.6.0b1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index fc9acf6d6..9f36a9102 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-17T15:21:35.979946599Z" +exclude-newer = "2026-04-27T07:52:36.360279576Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.5.1" +version = "4.6.0b1" source = { editable = "." } dependencies = [ { name = "backoff" }, From fdcecb73aedb034d5f010de0768b30fe68dc1b2a Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 8 May 2026 12:14:35 +0200 Subject: [PATCH 269/296] ci: allow prerelease branch releases (#1647) --- .github/workflows/release.yml | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efff90e16..80dd2d4d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,14 +45,40 @@ jobs: runs-on: ubuntu-latest environment: protected branches steps: - - name: Verify branch + - name: Verify release ref run: | - if [ "${GITHUB_REF}" != "refs/heads/main" ]; then - echo "❌ Error: Releases can only be triggered from main branch" + if [ "${GITHUB_REF_TYPE}" != "branch" ]; then + echo "❌ Error: Releases can only be triggered from branches" echo "Current ref: ${GITHUB_REF}" exit 1 fi + if [ "${GITHUB_REF_NAME}" = "main" ]; then + echo "✅ Official and pre-release releases are allowed from main" + exit 0 + fi + + case "${INPUTS_VERSION}" in + prepatch|preminor|premajor) + ;; + *) + echo "❌ Error: Official releases can only be triggered from main branch" + echo "Current branch: ${GITHUB_REF_NAME}" + echo "Requested version bump: ${INPUTS_VERSION}" + exit 1 + ;; + esac + + if [ -z "${INPUTS_PRERELEASE_TYPE}" ]; then + echo "❌ Error: Branch releases must specify prerelease_type as alpha, beta, or rc" + exit 1 + fi + + echo "✅ Pre-release branch release allowed from ${GITHUB_REF_NAME}" + env: + INPUTS_VERSION: ${{ inputs.version }} + INPUTS_PRERELEASE_TYPE: ${{ inputs.prerelease_type }} + - name: Confirm major release if: ${{ inputs.version == 'major' || inputs.version == 'premajor' }} run: | @@ -294,7 +320,7 @@ jobs: id: push-tag run: | git tag "v${STEPS_NEW_VERSION_OUTPUTS_VERSION}" - git push origin main + git push origin "HEAD:${GITHUB_REF_NAME}" git push origin "v${STEPS_NEW_VERSION_OUTPUTS_VERSION}" env: STEPS_NEW_VERSION_OUTPUTS_VERSION: ${{ steps.new-version.outputs.version }} From fd5decdd4f0a4eee05b4bb1feb7607c6cbe3944d Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Fri, 8 May 2026 16:07:36 +0200 Subject: [PATCH 270/296] fix(openai): handle Azure stream chunks without delta (#1648) --- langfuse/openai.py | 13 ++++-- tests/e2e/test_core_sdk.py | 3 +- tests/unit/test_openai.py | 93 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 1ce09f754..96fd55ce0 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -640,7 +640,9 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: chunk = chunk.__dict__ model = model or chunk.get("model", None) or None - usage = chunk.get("usage", None) + chunk_usage = chunk.get("usage", None) + if chunk_usage is not None: + usage = chunk_usage choices = chunk.get("choices", []) @@ -649,11 +651,16 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: choice = choice.__dict__ if resource.type == "chat": delta = choice.get("delta", None) - finish_reason = choice.get("finish_reason", None) + choice_finish_reason = choice.get("finish_reason", None) + if choice_finish_reason is not None: + finish_reason = choice_finish_reason - if _is_openai_v1(): + if _is_openai_v1() and delta is not None: delta = delta.__dict__ + if delta is None: + delta = {} + if delta.get("role", None) is not None: completion["role"] = delta["role"] diff --git a/tests/e2e/test_core_sdk.py b/tests/e2e/test_core_sdk.py index 9b491a5aa..614d6da41 100644 --- a/tests/e2e/test_core_sdk.py +++ b/tests/e2e/test_core_sdk.py @@ -2069,8 +2069,9 @@ def test_create_trace_sampling_zero(): } -def test_mask_function(): +def test_mask_function(request): LangfuseResourceManager.reset() + request.addfinalizer(LangfuseResourceManager.reset) def mask_func(data): if isinstance(data, dict): diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py index ce69b1c76..72923f425 100644 --- a/tests/unit/test_openai.py +++ b/tests/unit/test_openai.py @@ -101,6 +101,64 @@ def _make_chat_stream_chunks(): ] +def _make_chat_stream_chunks_with_trailing_content_filter_chunk(): + usage = SimpleNamespace(prompt_tokens=3, completion_tokens=1, total_tokens=4) + + return [ + SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role="assistant", + content="2", + function_call=None, + tool_calls=None, + ), + finish_reason=None, + ) + ], + usage=None, + ), + SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role=None, + content=None, + function_call=None, + tool_calls=None, + ), + finish_reason="stop", + ) + ], + usage=usage, + ), + SimpleNamespace( + model="", + choices=[ + SimpleNamespace( + delta=None, + finish_reason=None, + content_filter_offsets={ + "check_offset": 44, + "start_offset": 44, + "end_offset": 121, + }, + content_filter_results={ + "hate": {"filtered": False, "severity": "safe"}, + "self_harm": {"filtered": False, "severity": "safe"}, + "sexual": {"filtered": False, "severity": "safe"}, + "violence": {"filtered": False, "severity": "safe"}, + }, + ) + ], + usage=None, + ), + ] + + def _make_single_chunk_stream(): return SimpleNamespace( model="gpt-4o-mini", @@ -315,6 +373,41 @@ def test_openai_stream_preserves_original_stream_contract( } +def test_openai_stream_handles_trailing_azure_content_filter_chunk( + langfuse_memory_client, get_span, json_attr +): + openai_client = lf_openai.OpenAI(api_key="test") + raw_stream = DummyOpenAIStream( + _make_chat_stream_chunks_with_trailing_content_filter_chunk(), + DummySyncResponse(), + ) + + with patch.object(openai_client.chat.completions, "_post", return_value=raw_stream): + stream = openai_client.chat.completions.create( + name="unit-openai-native-stream-azure-filter", + model="gpt-4o-mini", + messages=[{"role": "user", "content": "1 + 1 = ?"}], + temperature=0, + stream=True, + ) + + chunks = list(stream) + stream.close() + + assert len(chunks) == 3 + + langfuse_memory_client.flush() + span = get_span("unit-openai-native-stream-azure-filter") + + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] == "2" + assert span.attributes["langfuse.observation.metadata.finish_reason"] == "stop" + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_USAGE_DETAILS) == { + "prompt_tokens": 3, + "completion_tokens": 1, + "total_tokens": 4, + } + + def test_openai_stream_break_still_finalizes_generation( langfuse_memory_client, get_span ): From 8bcc8fa84de7d72955ce76c0045e45b6730ffe76 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 8 May 2026 14:08:11 +0000 Subject: [PATCH 271/296] chore: release v4.6.1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 99c704c0e..e37c4215f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.6.0b1" +version = "4.6.1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 9f36a9102..29f567c44 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-04-27T07:52:36.360279576Z" +exclude-newer = "2026-05-01T14:08:03.903098393Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.6.0b1" +version = "4.6.1" source = { editable = "." } dependencies = [ { name = "backoff" }, From 56252acc7db325153758389383ca499c894797ac Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 13 May 2026 14:42:04 +0200 Subject: [PATCH 272/296] chore: add CODEOWNERS for GitHub config (#1652) chore: add codeowners for github config Co-authored-by: Codex Opus 4.6 (1M context) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b3e7ece6e..c7993b0b4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,5 @@ # Currently inactive # * @langfuse/maintainers + +# Require maintainer review for GitHub configuration changes +.github/ @langfuse/maintainers From 33859f26cb958e144bcf1516da655374ff3a096e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 09:28:28 +0200 Subject: [PATCH 273/296] chore(deps): bump the github-actions group across 1 directory with 5 updates (#1645) * chore(deps): bump the github-actions group across 1 directory with 5 updates Bumps the github-actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `8.0.0` | `8.1.0` | | [actions/cache](https://github.com/actions/cache) | `5.0.4` | `5.0.5` | | [github/codeql-action](https://github.com/github/codeql-action) | `4.35.1` | `4.35.4` | | [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) | `3.0.0` | `3.1.0` | | [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) | `3.0.1` | `3.0.3` | Updates `astral-sh/setup-uv` from 8.0.0 to 8.1.0 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/cec208311dfd045dd5311c1add060b2062131d57...08807647e7069bb48b6ef5acd8ec9567f424441b) Updates `actions/cache` from 5.0.4 to 5.0.5 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/668228422ae6a00e4ad889ee87cd7109ec5666a7...27d5ce7f107fe9357f9df03efb73ab90386fccae) Updates `github/codeql-action` from 4.35.1 to 4.35.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...68bde559dea0fdcac2102bfdf6230c5f70eb485e) Updates `dependabot/fetch-metadata` from 3.0.0 to 3.1.0 - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/ffa630c65fa7e0ecfa0625b5ceda64399aea1b36...25dd0e34f4fe68f24cc83900b1fe3fe149efef98) Updates `slackapi/slack-github-action` from 3.0.1 to 3.0.3 - [Release notes](https://github.com/slackapi/slack-github-action/releases) - [Changelog](https://github.com/slackapi/slack-github-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/slackapi/slack-github-action/compare/af78098f536edbc4de71162a307590698245be95...45a88b9581bfab2566dc881e2cd66d334e621e2c) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: astral-sh/setup-uv dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: dependabot/fetch-metadata dependency-version: 3.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: slackapi/slack-github-action dependency-version: 3.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] * ci: remove stale cache action version comment --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tobias Wochinger --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/codeql.yml | 4 ++-- .github/workflows/dependabot-merge.yml | 2 +- .github/workflows/release.yml | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b820059c..c4fca874d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.2" python-version: "3.13" @@ -41,12 +41,12 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.2" python-version: "3.13" enable-cache: true # zizmor: ignore[cache-poisoning] CI-only, no artifacts published - - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 # zizmor: ignore[cache-poisoning] + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # zizmor: ignore[cache-poisoning] name: Cache mypy cache with: path: ./.mypy_cache @@ -82,7 +82,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.2" python-version: ${{ matrix.python-version }} @@ -145,7 +145,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.2" python-version: "3.13" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 290bfee87..7081242de 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,7 +61,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -89,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 8eddf89f8..8e5040e77 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.0 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80dd2d4d4..3e764bd94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,7 +97,7 @@ jobs: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.11.2" python-version: "3.12" @@ -347,7 +347,7 @@ jobs: - name: Notify Slack on success if: success() - uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} webhook-type: incoming-webhook @@ -431,7 +431,7 @@ jobs: - name: Notify Slack on failure if: failure() - uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: webhook: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} webhook-type: incoming-webhook From b59a6114aafbe967db078c42d7e2200418257bef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 14:50:38 +0200 Subject: [PATCH 274/296] chore(deps): bump langsmith from 0.7.22 to 0.8.0 (#1653) Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from 0.7.22 to 0.8.0. - [Release notes](https://github.com/langchain-ai/langsmith-sdk/releases) - [Commits](https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.22...v0.8.0) --- updated-dependencies: - dependency-name: langsmith dependency-version: 0.8.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 29f567c44..00695f204 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-05-01T14:08:03.903098393Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P7D" [[package]] @@ -679,7 +679,7 @@ wheels = [ [[package]] name = "langsmith" -version = "0.7.22" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -692,9 +692,9 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923, upload-time = "2026-03-19T22:45:23.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/64/95f1f013531395f4e8ed73caeee780f65c7c58fe028cb543f8937b45611b/langsmith-0.8.0.tar.gz", hash = "sha256:59fe5b2a56bbbe14a08aa76691f84b49e8675dd21e11b57d80c6db8c08bac2e3", size = 4432996, upload-time = "2026-04-30T22:13:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927, upload-time = "2026-03-19T22:45:21.603Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e1/a4be2e696c9473bb53298df398237da5674704d781d4b748ed35aeef592a/langsmith-0.8.0-py3-none-any.whl", hash = "sha256:12cc4bc5622b835a6d841964d6034df3617bdb912dae0c1381fd0a68a9b3a3ef", size = 393268, upload-time = "2026-04-30T22:13:05.56Z" }, ] [[package]] From 246bef7b58436fb211d1ea272b991c76d758fcde Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Tue, 19 May 2026 15:52:29 +0200 Subject: [PATCH 275/296] feat(span-processor): mark app root spans (#1651) --- langfuse/_client/attributes.py | 1 + langfuse/_client/client.py | 74 ++-- langfuse/_client/propagation.py | 35 ++ langfuse/_client/span_processor.py | 186 +++++++-- tests/conftest.py | 4 + tests/live_provider/test_openai.py | 5 +- tests/unit/test_app_root_detection.py | 482 ++++++++++++++++++++++++ tests/unit/test_otel.py | 12 + tests/unit/test_propagate_attributes.py | 10 +- 9 files changed, 743 insertions(+), 66 deletions(-) create mode 100644 tests/unit/test_app_root_detection.py diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py index d34e8e403..4660b50f0 100644 --- a/langfuse/_client/attributes.py +++ b/langfuse/_client/attributes.py @@ -59,6 +59,7 @@ class LangfuseOtelSpanAttributes: # Internal AS_ROOT = "langfuse.internal.as_root" + IS_APP_ROOT = "langfuse.internal.is_app_root" # Experiments EXPERIMENT_ID = "langfuse.experiment.id" diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index 5f7c0f288..2f1c8d783 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -27,6 +27,7 @@ import backoff import httpx +from opentelemetry import context as otel_context_api from opentelemetry import trace as otel_trace_api from opentelemetry.sdk.trace import ReadableSpan, TracerProvider from opentelemetry.sdk.trace.export import SpanExporter @@ -66,7 +67,9 @@ ) from langfuse._client.propagation import ( PropagatedExperimentAttributes, + _detach_context_token_safely, _propagate_attributes, + _set_langfuse_trace_id_in_baggage, ) from langfuse._client.resource_manager import LangfuseResourceManager from langfuse._client.span import ( @@ -1178,39 +1181,54 @@ def _start_as_current_otel_span_with_processed_media( name=name, end_on_exit=end_on_exit if end_on_exit is not None else True, ) as otel_span: + baggage_token = None + + if otel_span.is_recording(): + context_with_app_root_claim = _set_langfuse_trace_id_in_baggage( + trace_id=self._get_otel_trace_id(otel_span), + context=otel_context_api.get_current(), + ) + baggage_token = otel_context_api.attach(context_with_app_root_claim) + span_class = self._get_span_class( as_type or "generation" ) # default was "generation" - common_args = { - "otel_span": otel_span, - "langfuse_client": self, - "environment": self._environment, - "release": self._release, - "input": input, - "output": output, - "metadata": metadata, - "version": version, - "level": level, - "status_message": status_message, - } - if span_class in [ - LangfuseGeneration, - LangfuseEmbedding, - ]: - common_args.update( - { - "completion_start_time": completion_start_time, - "model": model, - "model_parameters": model_parameters, - "usage_details": usage_details, - "cost_details": cost_details, - "prompt": prompt, - } - ) - # For span-like types (span, agent, tool, chain, retriever, evaluator, guardrail), no generation properties needed + try: + common_args = { + "otel_span": otel_span, + "langfuse_client": self, + "environment": self._environment, + "release": self._release, + "input": input, + "output": output, + "metadata": metadata, + "version": version, + "level": level, + "status_message": status_message, + } + + if span_class in [ + LangfuseGeneration, + LangfuseEmbedding, + ]: + common_args.update( + { + "completion_start_time": completion_start_time, + "model": model, + "model_parameters": model_parameters, + "usage_details": usage_details, + "cost_details": cost_details, + "prompt": prompt, + } + ) + # For span-like types (span, agent, tool, chain, retriever, evaluator, guardrail), no generation properties needed + + yield span_class(**common_args) # type: ignore[arg-type] - yield span_class(**common_args) # type: ignore[arg-type] + finally: + if baggage_token is not None: + _detach_context_token_safely(baggage_token) def _get_current_otel_span(self) -> Optional[otel_trace_api.Span]: current_span = otel_trace_api.get_current_span() diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 988f1f26e..597d8126e 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -316,6 +316,9 @@ def _get_propagated_attributes_from_context( # Handle baggage baggage_entries = baggage.get_all(context=context) for baggage_key, baggage_value in baggage_entries.items(): + if baggage_key == LANGFUSE_TRACE_ID_BAGGAGE_KEY: + continue + if baggage_key.startswith(LANGFUSE_BAGGAGE_PREFIX): span_key = _get_span_key_from_baggage_key(baggage_key) @@ -471,12 +474,44 @@ def _get_propagated_context_key(key: str) -> str: LANGFUSE_BAGGAGE_PREFIX = "langfuse_" +LANGFUSE_TRACE_ID_BAGGAGE_KEY = "langfuse_trace_id" def _get_propagated_baggage_key(key: str) -> str: return f"{LANGFUSE_BAGGAGE_PREFIX}{key}" +def _get_langfuse_trace_id_from_baggage( + context: otel_context_api.Context, +) -> Optional[str]: + value = otel_baggage_api.get_baggage( + name=LANGFUSE_TRACE_ID_BAGGAGE_KEY, + context=context, + ) + + if value is None: + return None + + return str(value).lower() + + +def _set_langfuse_trace_id_in_baggage( + *, + trace_id: str, + context: otel_context_api.Context, +) -> otel_context_api.Context: + normalized_trace_id = trace_id.lower() + + if _get_langfuse_trace_id_from_baggage(context) == normalized_trace_id: + return context + + return otel_baggage_api.set_baggage( + name=LANGFUSE_TRACE_ID_BAGGAGE_KEY, + value=normalized_trace_id, + context=context, + ) + + def _get_span_key_from_baggage_key(key: str) -> Optional[str]: if not key.startswith(LANGFUSE_BAGGAGE_PREFIX): return None diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index a684a8813..371b09566 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -13,27 +13,45 @@ import base64 import os -from typing import Callable, Dict, List, Optional +import threading +from dataclasses import dataclass +from typing import Callable, Dict, List, Optional, cast from opentelemetry import context as context_api from opentelemetry.context import Context from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import ReadableSpan, Span from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter -from opentelemetry.trace import format_span_id +from opentelemetry.trace import format_span_id, format_trace_id +from langfuse._client.attributes import LangfuseOtelSpanAttributes from langfuse._client.environment_variables import ( LANGFUSE_FLUSH_AT, LANGFUSE_FLUSH_INTERVAL, LANGFUSE_OTEL_TRACES_EXPORT_PATH, ) -from langfuse._client.propagation import _get_propagated_attributes_from_context +from langfuse._client.propagation import ( + _get_langfuse_trace_id_from_baggage, + _get_propagated_attributes_from_context, +) from langfuse._client.span_filter import is_default_export_span, is_langfuse_span from langfuse._client.utils import span_formatter from langfuse._version import __version__ as langfuse_version from langfuse.logger import langfuse_logger +@dataclass +class _AppRootSpanState: + expected_exported_at_start: bool + ended: bool = False + + +@dataclass +class _AppRootTraceState: + active_count: int + spans: Dict[str, _AppRootSpanState] + + class LangfuseSpanProcessor(BatchSpanProcessor): """OpenTelemetry span processor that exports spans to the Langfuse API. @@ -73,6 +91,9 @@ def __init__( ) self._should_export_span = should_export_span or is_default_export_span + self._app_root_lock = threading.Lock() + self._app_root_traces: Dict[str, _AppRootTraceState] = {} + env_flush_at = os.environ.get(LANGFUSE_FLUSH_AT, None) flush_at = flush_at or int(env_flush_at) if env_flush_at is not None else None @@ -133,51 +154,146 @@ def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None f"Propagated {len(propagated_attributes)} attributes to span '{format_span_id(span.context.span_id)}': {propagated_attributes}" ) + try: + self._mark_app_root_candidate(span=span, parent_context=context) + except Exception as error: + langfuse_logger.debug( + "Trace: app-root start-time check failed. Span will not be marked as app root | " + f"span_name='{getattr(span, 'name', '')}' | " + f"Error: {error}" + ) + return super().on_start(span, parent_context) def on_end(self, span: ReadableSpan) -> None: - # Only export spans that belong to the scoped project - # This is important to not send spans to wrong project in multi-project setups - if is_langfuse_span(span) and not self._is_langfuse_project_span(span): + try: + # Only export spans that belong to the scoped project + # This is important to not send spans to wrong project in multi-project setups + if is_langfuse_span(span) and not self._is_langfuse_project_span(span): + langfuse_logger.debug( + f"Security: Span rejected - belongs to project '{span.instrumentation_scope.attributes.get('public_key') if span.instrumentation_scope and span.instrumentation_scope.attributes else None}' but processor is for '{self.public_key}'. " + f"This prevents cross-project data leakage in multi-project environments." + ) + return + + # Do not export spans from blocked instrumentation scopes + if self._is_blocked_instrumentation_scope(span): + langfuse_logger.debug( + "Trace: Dropping span due to blocked instrumentation scope | " + f"span_name='{span.name}' | " + f"instrumentation_scope='{self._get_scope_name(span)}'" + ) + return + + # Apply custom or default span filter + try: + should_export = self._should_export_span(span) + except Exception as error: + langfuse_logger.error( + "Trace: should_export_span callback raised an error. " + f"Dropping span name='{span.name}' scope='{self._get_scope_name(span)}'. " + f"Error: {error}" + ) + return + + if not should_export: + langfuse_logger.debug( + "Trace: Dropping span due to should_export_span filter | " + f"span_name='{span.name}' | " + f"instrumentation_scope='{self._get_scope_name(span)}'" + ) + return + langfuse_logger.debug( - f"Security: Span rejected - belongs to project '{span.instrumentation_scope.attributes.get('public_key') if span.instrumentation_scope and span.instrumentation_scope.attributes else None}' but processor is for '{self.public_key}'. " - f"This prevents cross-project data leakage in multi-project environments." + f"Trace: Processing span name='{span._name}' | Full details:\n{span_formatter(span)}" ) - return - # Do not export spans from blocked instrumentation scopes - if self._is_blocked_instrumentation_scope(span): - langfuse_logger.debug( - "Trace: Dropping span due to blocked instrumentation scope | " - f"span_name='{span.name}' | " - f"instrumentation_scope='{self._get_scope_name(span)}'" + super().on_end(span) + finally: + self._cleanup_app_root_state(span) + + def _mark_app_root_candidate(self, *, span: Span, parent_context: Context) -> None: + trace_id = format_trace_id(span.context.trace_id) + span_id = format_span_id(span.context.span_id) + parent_span_id = format_span_id(span.parent.span_id) if span.parent else None + expected_exported = self._is_expected_exported_at_start(span) + propagated_trace_id = _get_langfuse_trace_id_from_baggage(parent_context) + + with self._app_root_lock: + trace_state = self._app_root_traces.get(trace_id) + + if trace_state is None: + trace_state = _AppRootTraceState(active_count=0, spans={}) + self._app_root_traces[trace_id] = trace_state + + parent_state = ( + trace_state.spans.get(parent_span_id) + if parent_span_id is not None + else None ) - return + parent_expected_exported = ( + parent_state.expected_exported_at_start is True + if parent_state is not None + else False + ) + suppressed_by_parent_claim = propagated_trace_id == trace_id - # Apply custom or default span filter - try: - should_export = self._should_export_span(span) - except Exception as error: - langfuse_logger.error( - "Trace: should_export_span callback raised an error. " - f"Dropping span name='{span.name}' scope='{self._get_scope_name(span)}'. " - f"Error: {error}" + mark_app_root = ( + expected_exported + and not parent_expected_exported + and not suppressed_by_parent_claim ) - return - if not should_export: - langfuse_logger.debug( - "Trace: Dropping span due to should_export_span filter | " - f"span_name='{span.name}' | " - f"instrumentation_scope='{self._get_scope_name(span)}'" + trace_state.spans[span_id] = _AppRootSpanState( + expected_exported_at_start=expected_exported, ) - return + trace_state.active_count += 1 - langfuse_logger.debug( - f"Trace: Processing span name='{span._name}' | Full details:\n{span_formatter(span)}" - ) + if mark_app_root: + span.set_attribute(LangfuseOtelSpanAttributes.IS_APP_ROOT, True) + + def _cleanup_app_root_state(self, span: ReadableSpan) -> None: + trace_id = format_trace_id(span.context.trace_id) + span_id = format_span_id(span.context.span_id) + + with self._app_root_lock: + trace_state = self._app_root_traces.get(trace_id) + + if trace_state is None: + return + + span_state = trace_state.spans.get(span_id) + + if span_state is not None and not span_state.ended: + span_state.ended = True + trace_state.active_count -= 1 + + if trace_state.active_count <= 0: + self._app_root_traces.pop(trace_id, None) + + def _is_expected_exported_at_start(self, span: Span) -> bool: + readable_span = cast(ReadableSpan, span) - super().on_end(span) + if is_langfuse_span(readable_span) and not self._is_langfuse_project_span( + readable_span + ): + return False + + if self._is_blocked_instrumentation_scope(readable_span): + return False + + try: + return bool(self._should_export_span(readable_span)) + except Exception as error: + langfuse_logger.debug( + "Trace: should_export_span callback raised during app-root " + f"start-time check. Span will not be marked as app root | " + f"span_name='{readable_span.name}' | " + f"instrumentation_scope='{self._get_scope_name(readable_span)}' | " + f"Error: {error}" + ) + + return False def _is_blocked_instrumentation_scope(self, span: ReadableSpan) -> bool: return ( diff --git a/tests/conftest.py b/tests/conftest.py index 1aea59889..ff97ddbd6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -95,6 +95,8 @@ def langfuse_memory_client( tracer_provider = TracerProvider(resource=Resource.create({"service.name": "test"})) def mock_init(self: Any, **kwargs: Any) -> None: + import threading + from opentelemetry.sdk.trace.export import BatchSpanProcessor from langfuse._client.span_filter import is_default_export_span @@ -107,6 +109,8 @@ def mock_init(self: Any, **kwargs: Any) -> None: self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) + self._app_root_lock = threading.Lock() + self._app_root_traces = {} BatchSpanProcessor.__init__( self, span_exporter=memory_exporter, diff --git a/tests/live_provider/test_openai.py b/tests/live_provider/test_openai.py index 3cc05c9c6..3575cfbb4 100644 --- a/tests/live_provider/test_openai.py +++ b/tests/live_provider/test_openai.py @@ -1237,10 +1237,11 @@ def test_audio_input_and_output(openai): content_path = "static/joke_prompt.wav" base64_string = encode_file_to_base64(content_path) + model = "gpt-audio-2025-08-28" client.chat.completions.create( name=generation_name, - model="gpt-4o-audio-preview", + model=model, modalities=["text", "audio"], audio={"voice": "alloy", "format": "wav"}, messages=[ @@ -1274,7 +1275,7 @@ def test_audio_input_and_output(openai): in generation.data[0].input[0]["content"][1]["input_audio"]["data"] ) assert generation.data[0].type == "GENERATION" - assert "gpt-4o-audio-preview" in generation.data[0].model + assert generation.data[0].model == model assert generation.data[0].start_time is not None assert generation.data[0].end_time is not None assert generation.data[0].start_time < generation.data[0].end_time diff --git a/tests/unit/test_app_root_detection.py b/tests/unit/test_app_root_detection.py new file mode 100644 index 000000000..a320807ed --- /dev/null +++ b/tests/unit/test_app_root_detection.py @@ -0,0 +1,482 @@ +import threading +from concurrent.futures import ThreadPoolExecutor + +from opentelemetry import baggage +from opentelemetry import context as otel_context_api +from opentelemetry import trace as trace_api +from opentelemetry.sdk.trace import ReadableSpan, TracerProvider +from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags + +from langfuse._client.attributes import LangfuseOtelSpanAttributes +from langfuse._client.constants import LANGFUSE_TRACER_NAME +from langfuse._client.propagation import ( + LANGFUSE_TRACE_ID_BAGGAGE_KEY, + _get_langfuse_trace_id_from_baggage, + _set_langfuse_trace_id_in_baggage, +) +from langfuse._client.span_processor import LangfuseSpanProcessor + +PUBLIC_KEY = "test-public-key" +SECRET_KEY = "test-secret-key" + + +def _create_processor(memory_exporter, **kwargs): + tracer_provider = TracerProvider() + processor = LangfuseSpanProcessor( + public_key=PUBLIC_KEY, + secret_key=SECRET_KEY, + base_url="http://test-host", + span_exporter=memory_exporter, + **kwargs, + ) + tracer_provider.add_span_processor(processor) + + return tracer_provider, processor + + +def _get_spans_by_name(memory_exporter): + return {span.name: span for span in memory_exporter.get_finished_spans()} + + +def _langfuse_tracer(tracer_provider): + return tracer_provider.get_tracer( + LANGFUSE_TRACER_NAME, + "test", + attributes={"public_key": PUBLIC_KEY}, + ) + + +def test_filtered_parent_marks_exported_children_as_app_roots(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + filtered_tracer = tracer_provider.get_tracer("requests") + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with filtered_tracer.start_as_current_span("filtered-parent"): + with langfuse_tracer.start_as_current_span("child-a"): + pass + + with langfuse_tracer.start_as_current_span("child-b"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "filtered-parent" not in spans + assert spans["child-a"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert spans["child-b"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert processor._app_root_traces == {} + + +def test_exported_parent_suppresses_exported_child_app_root(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with langfuse_tracer.start_as_current_span("parent"): + with langfuse_tracer.start_as_current_span("child"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert spans["parent"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes + assert processor._app_root_traces == {} + + +def test_grandparent_baggage_claim_suppresses_child_through_filtered_parent( + memory_exporter, +): + tracer_provider, processor = _create_processor(memory_exporter) + filtered_tracer = tracer_provider.get_tracer("requests") + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with langfuse_tracer.start_as_current_span("grandparent") as grandparent: + context_with_claim = baggage.set_baggage( + name=LANGFUSE_TRACE_ID_BAGGAGE_KEY, + value=format(grandparent.context.trace_id, "032x"), + context=otel_context_api.get_current(), + ) + token = otel_context_api.attach(context_with_claim) + try: + with filtered_tracer.start_as_current_span("filtered-parent"): + with langfuse_tracer.start_as_current_span("child"): + pass + finally: + otel_context_api.detach(token) + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "filtered-parent" not in spans + assert ( + spans["grandparent"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + ) + assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes + assert processor._app_root_traces == {} + + +def test_same_trace_baggage_claim_suppresses_local_app_root(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + trace_id = int("1" * 32, 16) + parent_context = _remote_parent_context(trace_id=trace_id) + parent_context = baggage.set_baggage( + name=LANGFUSE_TRACE_ID_BAGGAGE_KEY, + value=format(trace_id, "032x"), + context=parent_context, + ) + + span = langfuse_tracer.start_span("downstream-root", context=parent_context) + span.end() + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert ( + LangfuseOtelSpanAttributes.IS_APP_ROOT + not in spans["downstream-root"].attributes + ) + assert processor._app_root_traces == {} + + +def test_different_trace_baggage_claim_does_not_suppress_local_app_root( + memory_exporter, +): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + trace_id = int("1" * 32, 16) + parent_context = _remote_parent_context(trace_id=trace_id) + parent_context = baggage.set_baggage( + name=LANGFUSE_TRACE_ID_BAGGAGE_KEY, + value="2" * 32, + context=parent_context, + ) + + span = langfuse_tracer.start_span("downstream-root", context=parent_context) + span.end() + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert ( + spans["downstream-root"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] + is True + ) + assert processor._app_root_traces == {} + + +def test_local_baggage_claim_suppresses_child_even_when_parent_is_filtered( + memory_exporter, +): + def should_export_span(span: ReadableSpan) -> bool: + return span.name != "parent" + + tracer_provider, processor = _create_processor( + memory_exporter, + should_export_span=should_export_span, + ) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with langfuse_tracer.start_as_current_span("parent") as parent: + context_with_claim = baggage.set_baggage( + name=LANGFUSE_TRACE_ID_BAGGAGE_KEY, + value=format(parent.context.trace_id, "032x"), + context=otel_context_api.get_current(), + ) + token = otel_context_api.attach(context_with_claim) + + try: + with langfuse_tracer.start_as_current_span("child"): + pass + finally: + otel_context_api.detach(token) + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "parent" not in spans + assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes + assert processor._app_root_traces == {} + + +def test_start_time_false_positive_can_leave_exported_child_without_app_root( + memory_exporter, +): + def should_export_span(span: ReadableSpan) -> bool: + if span.name == "parent": + return span.end_time is None + + return True + + tracer_provider, processor = _create_processor( + memory_exporter, + should_export_span=should_export_span, + ) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with langfuse_tracer.start_as_current_span("parent"): + with langfuse_tracer.start_as_current_span("child"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "parent" not in spans + assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes + assert processor._app_root_traces == {} + + +def test_active_langfuse_scope_sets_baggage_after_root_start( + langfuse_memory_client, + memory_exporter, +): + with langfuse_memory_client.start_as_current_observation(name="root") as root: + baggage_entries = baggage.get_all(context=otel_context_api.get_current()) + + assert baggage_entries[LANGFUSE_TRACE_ID_BAGGAGE_KEY] == root.trace_id + + with langfuse_memory_client.start_as_current_observation(name="child"): + pass + + langfuse_memory_client.flush() + + spans = _get_spans_by_name(memory_exporter) + + assert spans["root"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes + assert "langfuse.trace.metadata.trace_id" not in spans["child"].attributes + + +def test_blocked_instrumentation_scope_parent_marks_child_as_app_root( + memory_exporter, +): + tracer_provider, processor = _create_processor( + memory_exporter, + blocked_instrumentation_scopes=["blocked.scope"], + ) + blocked_tracer = tracer_provider.get_tracer("blocked.scope") + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with blocked_tracer.start_as_current_span("blocked-parent"): + with langfuse_tracer.start_as_current_span("child"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "blocked-parent" not in spans + assert spans["child"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert processor._app_root_traces == {} + + +def test_foreign_project_langfuse_parent_marks_child_as_app_root(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + foreign_tracer = tracer_provider.get_tracer( + LANGFUSE_TRACER_NAME, + "test", + attributes={"public_key": "different-public-key"}, + ) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with foreign_tracer.start_as_current_span("foreign-parent"): + with langfuse_tracer.start_as_current_span("child"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "foreign-parent" not in spans + assert spans["child"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert processor._app_root_traces == {} + + +def test_should_export_span_raising_does_not_mark_app_root(memory_exporter): + def should_export_span(span: ReadableSpan) -> bool: + raise RuntimeError("boom") + + tracer_provider, processor = _create_processor( + memory_exporter, + should_export_span=should_export_span, + ) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + with langfuse_tracer.start_as_current_span("root"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "root" not in spans + assert processor._app_root_traces == {} + + +def test_mark_app_root_candidate_exception_is_swallowed(memory_exporter, monkeypatch): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + def raise_boom(*args, **kwargs): + raise RuntimeError("boom") + + monkeypatch.setattr(processor, "_mark_app_root_candidate", raise_boom) + + with langfuse_tracer.start_as_current_span("root"): + pass + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert "root" in spans + assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["root"].attributes + + +def test_concurrent_traces_keep_state_consistent(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + thread_count = 16 + spans_per_thread = 25 + barrier = threading.Barrier(thread_count) + + def worker(worker_id: int) -> None: + barrier.wait() + for span_index in range(spans_per_thread): + with langfuse_tracer.start_as_current_span( + f"root-{worker_id}-{span_index}" + ): + with langfuse_tracer.start_as_current_span( + f"child-{worker_id}-{span_index}" + ): + pass + + with ThreadPoolExecutor(max_workers=thread_count) as pool: + list(pool.map(worker, range(thread_count))) + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + expected_total = thread_count * spans_per_thread + root_spans = [span for name, span in spans.items() if name.startswith("root-")] + child_spans = [span for name, span in spans.items() if name.startswith("child-")] + + assert len(root_spans) == expected_total + assert len(child_spans) == expected_total + assert all( + span.attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + for span in root_spans + ) + assert all( + LangfuseOtelSpanAttributes.IS_APP_ROOT not in span.attributes + for span in child_spans + ) + assert processor._app_root_traces == {} + + +def test_multiple_interleaved_traces_track_state_independently(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + trace_a_root = langfuse_tracer.start_span("trace-a-root") + trace_a_ctx = trace_api.set_span_in_context(trace_a_root) + trace_b_root = langfuse_tracer.start_span("trace-b-root") + trace_b_ctx = trace_api.set_span_in_context(trace_b_root) + + assert len(processor._app_root_traces) == 2 + + trace_a_child = langfuse_tracer.start_span("trace-a-child", context=trace_a_ctx) + trace_b_child = langfuse_tracer.start_span("trace-b-child", context=trace_b_ctx) + + trace_b_child.end() + trace_b_root.end() + + assert len(processor._app_root_traces) == 1 + + trace_a_child.end() + trace_a_root.end() + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert ( + spans["trace-a-root"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + ) + assert ( + spans["trace-b-root"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + ) + assert ( + LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["trace-a-child"].attributes + ) + assert ( + LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["trace-b-child"].attributes + ) + assert processor._app_root_traces == {} + + +def test_set_langfuse_trace_id_in_baggage_sets_value(): + trace_id = "a" * 32 + context = _set_langfuse_trace_id_in_baggage( + trace_id=trace_id, + context=otel_context_api.Context(), + ) + + assert _get_langfuse_trace_id_from_baggage(context) == trace_id + + +def test_set_langfuse_trace_id_in_baggage_normalizes_case(): + context = _set_langfuse_trace_id_in_baggage( + trace_id="ABCDEF" + "0" * 26, + context=otel_context_api.Context(), + ) + + assert _get_langfuse_trace_id_from_baggage(context) == "abcdef" + "0" * 26 + + +def test_set_langfuse_trace_id_in_baggage_is_idempotent_for_same_trace(): + trace_id = "a" * 32 + context = _set_langfuse_trace_id_in_baggage( + trace_id=trace_id, + context=otel_context_api.Context(), + ) + + same_context = _set_langfuse_trace_id_in_baggage( + trace_id=trace_id, + context=context, + ) + + assert same_context is context + + +def test_set_langfuse_trace_id_in_baggage_overwrites_for_different_trace(): + first = _set_langfuse_trace_id_in_baggage( + trace_id="a" * 32, + context=otel_context_api.Context(), + ) + + second = _set_langfuse_trace_id_in_baggage( + trace_id="b" * 32, + context=first, + ) + + assert second is not first + assert _get_langfuse_trace_id_from_baggage(second) == "b" * 32 + + +def _remote_parent_context(*, trace_id: int): + span_context = SpanContext( + trace_id=trace_id, + span_id=int("a" * 16, 16), + is_remote=True, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + ) + + return trace_api.set_span_in_context(NonRecordingSpan(span_context)) diff --git a/tests/unit/test_otel.py b/tests/unit/test_otel.py index e7eb74280..92576815f 100644 --- a/tests/unit/test_otel.py +++ b/tests/unit/test_otel.py @@ -88,6 +88,8 @@ def mock_processor_init(self, monkeypatch, memory_exporter): """Mock the LangfuseSpanProcessor initialization to avoid HTTP traffic.""" def mock_init(self, **kwargs): + import threading + from opentelemetry.sdk.trace.export import BatchSpanProcessor from langfuse._client.span_filter import is_default_export_span @@ -100,6 +102,8 @@ def mock_init(self, **kwargs): self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) + self._app_root_lock = threading.Lock() + self._app_root_traces = {} BatchSpanProcessor.__init__( self, span_exporter=memory_exporter, @@ -1990,6 +1994,8 @@ def multi_project_setup(self, monkeypatch): # Setup tracers with appropriate project-specific span exporting def mock_processor_init(self, **kwargs): + import threading + from opentelemetry.sdk.trace.export import BatchSpanProcessor from langfuse._client.span_filter import is_default_export_span @@ -1998,6 +2004,8 @@ def mock_processor_init(self, **kwargs): self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) + self._app_root_lock = threading.Lock() + self._app_root_traces = {} # Use the appropriate exporter based on the project key if self.public_key == project1_key: exporter = exporter_project1 @@ -2365,6 +2373,8 @@ def instrumentation_filtering_setup(self, monkeypatch): # Mock the LangfuseSpanProcessor to use our test exporters def mock_processor_init(self, **kwargs): + import threading + from opentelemetry.sdk.trace.export import BatchSpanProcessor from langfuse._client.span_filter import is_default_export_span @@ -2377,6 +2387,8 @@ def mock_processor_init(self, **kwargs): self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) + self._app_root_lock = threading.Lock() + self._app_root_traces = {} # For testing, use the appropriate exporter based on setup exporter = kwargs.get("_test_exporter", blocked_exporter) diff --git a/tests/unit/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py index e1085b879..c783e65dd 100644 --- a/tests/unit/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -1638,6 +1638,8 @@ def test_baggage_disabled_by_default(self, langfuse_client): from opentelemetry import baggage from opentelemetry import context as otel_context + from langfuse._client.propagation import LANGFUSE_TRACE_ID_BAGGAGE_KEY + with langfuse_client.start_as_current_observation(name="parent"): with propagate_attributes( user_id="user_123", @@ -1646,7 +1648,13 @@ def test_baggage_disabled_by_default(self, langfuse_client): # Get current context and inspect baggage current_context = otel_context.get_current() baggage_entries = baggage.get_all(context=current_context) - assert len(baggage_entries) == 0 + user_baggage_entries = { + key: value + for key, value in baggage_entries.items() + if key != LANGFUSE_TRACE_ID_BAGGAGE_KEY + } + + assert user_baggage_entries == {} def test_metadata_key_with_user_id_substring_doesnt_collide( self, langfuse_client, memory_exporter From fc86ef89a385cdbdd50b88a82b2f46c04f66de23 Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 20 May 2026 11:31:26 +0200 Subject: [PATCH 276/296] feat(span-processor): simplify app root state and expand LLM scopes (#1662) --- langfuse/_client/span_filter.py | 27 ++++++++++++- langfuse/_client/span_processor.py | 53 +++----------------------- tests/conftest.py | 2 +- tests/unit/test_app_root_detection.py | 55 +++++++++++++++++++-------- tests/unit/test_otel.py | 6 +-- tests/unit/test_span_filter.py | 53 ++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 67 deletions(-) diff --git a/langfuse/_client/span_filter.py b/langfuse/_client/span_filter.py index 0bd3fc558..8ac384740 100644 --- a/langfuse/_client/span_filter.py +++ b/langfuse/_client/span_filter.py @@ -12,12 +12,37 @@ { LANGFUSE_TRACER_NAME, "agent_framework", + "autogen-core", "ai", "haystack", "langsmith", "litellm", "openinference", + "opentelemetry.instrumentation.agno", + "opentelemetry.instrumentation.alephalpha", "opentelemetry.instrumentation.anthropic", + "opentelemetry.instrumentation.bedrock", + "opentelemetry.instrumentation.cohere", + "opentelemetry.instrumentation.crewai", + "opentelemetry.instrumentation.google_generativeai", + "opentelemetry.instrumentation.groq", + "opentelemetry.instrumentation.haystack", + "opentelemetry.instrumentation.langchain", + "opentelemetry.instrumentation.llamaindex", + "opentelemetry.instrumentation.mistralai", + "opentelemetry.instrumentation.ollama", + "opentelemetry.instrumentation.openai", + "opentelemetry.instrumentation.openai_agents", + "opentelemetry.instrumentation.openai_v2", + "opentelemetry.instrumentation.replicate", + "opentelemetry.instrumentation.sagemaker", + "opentelemetry.instrumentation.together", + "opentelemetry.instrumentation.transformers", + "opentelemetry.instrumentation.vertexai", + "opentelemetry.instrumentation.voyageai", + "opentelemetry.instrumentation.watsonx", + "opentelemetry.instrumentation.writer", + "pydantic-ai", "strands-agents", "vllm", } @@ -28,7 +53,7 @@ - exact match (``scope == prefix``) - direct descendant scopes (``scope.startswith(prefix + ".")``) -Please create a Github issue in https://github.com/langfuse/langfuse if you'd like to expand this default allow list. +Please create a GitHub issue in https://github.com/langfuse/langfuse if you'd like to expand this default allow list. """ diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index 371b09566..42a1d2cf7 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -14,7 +14,6 @@ import base64 import os import threading -from dataclasses import dataclass from typing import Callable, Dict, List, Optional, cast from opentelemetry import context as context_api @@ -40,18 +39,6 @@ from langfuse.logger import langfuse_logger -@dataclass -class _AppRootSpanState: - expected_exported_at_start: bool - ended: bool = False - - -@dataclass -class _AppRootTraceState: - active_count: int - spans: Dict[str, _AppRootSpanState] - - class LangfuseSpanProcessor(BatchSpanProcessor): """OpenTelemetry span processor that exports spans to the Langfuse API. @@ -92,7 +79,7 @@ def __init__( self._should_export_span = should_export_span or is_default_export_span self._app_root_lock = threading.Lock() - self._app_root_traces: Dict[str, _AppRootTraceState] = {} + self._span_export_expectation_by_id: Dict[str, bool] = {} env_flush_at = os.environ.get(LANGFUSE_FLUSH_AT, None) flush_at = flush_at or int(env_flush_at) if env_flush_at is not None else None @@ -220,56 +207,28 @@ def _mark_app_root_candidate(self, *, span: Span, parent_context: Context) -> No propagated_trace_id = _get_langfuse_trace_id_from_baggage(parent_context) with self._app_root_lock: - trace_state = self._app_root_traces.get(trace_id) - - if trace_state is None: - trace_state = _AppRootTraceState(active_count=0, spans={}) - self._app_root_traces[trace_id] = trace_state - - parent_state = ( - trace_state.spans.get(parent_span_id) - if parent_span_id is not None - else None - ) parent_expected_exported = ( - parent_state.expected_exported_at_start is True - if parent_state is not None - else False + parent_span_id is not None + and self._span_export_expectation_by_id.get(parent_span_id) is True ) suppressed_by_parent_claim = propagated_trace_id == trace_id + self._span_export_expectation_by_id[span_id] = expected_exported + mark_app_root = ( expected_exported and not parent_expected_exported and not suppressed_by_parent_claim ) - trace_state.spans[span_id] = _AppRootSpanState( - expected_exported_at_start=expected_exported, - ) - trace_state.active_count += 1 - if mark_app_root: span.set_attribute(LangfuseOtelSpanAttributes.IS_APP_ROOT, True) def _cleanup_app_root_state(self, span: ReadableSpan) -> None: - trace_id = format_trace_id(span.context.trace_id) span_id = format_span_id(span.context.span_id) with self._app_root_lock: - trace_state = self._app_root_traces.get(trace_id) - - if trace_state is None: - return - - span_state = trace_state.spans.get(span_id) - - if span_state is not None and not span_state.ended: - span_state.ended = True - trace_state.active_count -= 1 - - if trace_state.active_count <= 0: - self._app_root_traces.pop(trace_id, None) + self._span_export_expectation_by_id.pop(span_id, None) def _is_expected_exported_at_start(self, span: Span) -> bool: readable_span = cast(ReadableSpan, span) diff --git a/tests/conftest.py b/tests/conftest.py index ff97ddbd6..0163842c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -110,7 +110,7 @@ def mock_init(self: Any, **kwargs: Any) -> None: kwargs.get("should_export_span") or is_default_export_span ) self._app_root_lock = threading.Lock() - self._app_root_traces = {} + self._span_export_expectation_by_id = {} BatchSpanProcessor.__init__( self, span_exporter=memory_exporter, diff --git a/tests/unit/test_app_root_detection.py b/tests/unit/test_app_root_detection.py index a320807ed..334237c5d 100644 --- a/tests/unit/test_app_root_detection.py +++ b/tests/unit/test_app_root_detection.py @@ -65,7 +65,7 @@ def test_filtered_parent_marks_exported_children_as_app_roots(memory_exporter): assert "filtered-parent" not in spans assert spans["child-a"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True assert spans["child-b"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_exported_parent_suppresses_exported_child_app_root(memory_exporter): @@ -82,7 +82,7 @@ def test_exported_parent_suppresses_exported_child_app_root(memory_exporter): assert spans["parent"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_grandparent_baggage_claim_suppresses_child_through_filtered_parent( @@ -115,7 +115,7 @@ def test_grandparent_baggage_claim_suppresses_child_through_filtered_parent( spans["grandparent"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True ) assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_same_trace_baggage_claim_suppresses_local_app_root(memory_exporter): @@ -139,7 +139,7 @@ def test_same_trace_baggage_claim_suppresses_local_app_root(memory_exporter): LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["downstream-root"].attributes ) - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_different_trace_baggage_claim_does_not_suppress_local_app_root( @@ -165,7 +165,7 @@ def test_different_trace_baggage_claim_does_not_suppress_local_app_root( spans["downstream-root"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True ) - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_local_baggage_claim_suppresses_child_even_when_parent_is_filtered( @@ -200,7 +200,7 @@ def should_export_span(span: ReadableSpan) -> bool: assert "parent" not in spans assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_start_time_false_positive_can_leave_exported_child_without_app_root( @@ -228,7 +228,7 @@ def should_export_span(span: ReadableSpan) -> bool: assert "parent" not in spans assert LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["child"].attributes - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_active_langfuse_scope_sets_baggage_after_root_start( @@ -272,7 +272,7 @@ def test_blocked_instrumentation_scope_parent_marks_child_as_app_root( assert "blocked-parent" not in spans assert spans["child"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_foreign_project_langfuse_parent_marks_child_as_app_root(memory_exporter): @@ -294,7 +294,7 @@ def test_foreign_project_langfuse_parent_marks_child_as_app_root(memory_exporter assert "foreign-parent" not in spans assert spans["child"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_should_export_span_raising_does_not_mark_app_root(memory_exporter): @@ -315,7 +315,7 @@ def should_export_span(span: ReadableSpan) -> bool: spans = _get_spans_by_name(memory_exporter) assert "root" not in spans - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} def test_mark_app_root_candidate_exception_is_swallowed(memory_exporter, monkeypatch): @@ -378,10 +378,12 @@ def worker(worker_id: int) -> None: LangfuseOtelSpanAttributes.IS_APP_ROOT not in span.attributes for span in child_spans ) - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} -def test_multiple_interleaved_traces_track_state_independently(memory_exporter): +def test_multiple_interleaved_traces_track_active_span_state_independently( + memory_exporter, +): tracer_provider, processor = _create_processor(memory_exporter) langfuse_tracer = _langfuse_tracer(tracer_provider) @@ -390,15 +392,17 @@ def test_multiple_interleaved_traces_track_state_independently(memory_exporter): trace_b_root = langfuse_tracer.start_span("trace-b-root") trace_b_ctx = trace_api.set_span_in_context(trace_b_root) - assert len(processor._app_root_traces) == 2 + assert len(processor._span_export_expectation_by_id) == 2 trace_a_child = langfuse_tracer.start_span("trace-a-child", context=trace_a_ctx) trace_b_child = langfuse_tracer.start_span("trace-b-child", context=trace_b_ctx) + assert len(processor._span_export_expectation_by_id) == 4 + trace_b_child.end() trace_b_root.end() - assert len(processor._app_root_traces) == 1 + assert len(processor._span_export_expectation_by_id) == 2 trace_a_child.end() trace_a_root.end() @@ -419,7 +423,28 @@ def test_multiple_interleaved_traces_track_state_independently(memory_exporter): assert ( LangfuseOtelSpanAttributes.IS_APP_ROOT not in spans["trace-b-child"].attributes ) - assert processor._app_root_traces == {} + assert processor._span_export_expectation_by_id == {} + + +def test_child_started_after_parent_end_is_marked_as_app_root(memory_exporter): + tracer_provider, processor = _create_processor(memory_exporter) + langfuse_tracer = _langfuse_tracer(tracer_provider) + + parent = langfuse_tracer.start_span("parent") + parent_ctx = trace_api.set_span_in_context(parent) + + parent.end() + + child = langfuse_tracer.start_span("child", context=parent_ctx) + child.end() + + processor.force_flush() + + spans = _get_spans_by_name(memory_exporter) + + assert spans["parent"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert spans["child"].attributes[LangfuseOtelSpanAttributes.IS_APP_ROOT] is True + assert processor._span_export_expectation_by_id == {} def test_set_langfuse_trace_id_in_baggage_sets_value(): diff --git a/tests/unit/test_otel.py b/tests/unit/test_otel.py index 92576815f..f752df607 100644 --- a/tests/unit/test_otel.py +++ b/tests/unit/test_otel.py @@ -103,7 +103,7 @@ def mock_init(self, **kwargs): kwargs.get("should_export_span") or is_default_export_span ) self._app_root_lock = threading.Lock() - self._app_root_traces = {} + self._span_export_expectation_by_id = {} BatchSpanProcessor.__init__( self, span_exporter=memory_exporter, @@ -2005,7 +2005,7 @@ def mock_processor_init(self, **kwargs): kwargs.get("should_export_span") or is_default_export_span ) self._app_root_lock = threading.Lock() - self._app_root_traces = {} + self._span_export_expectation_by_id = {} # Use the appropriate exporter based on the project key if self.public_key == project1_key: exporter = exporter_project1 @@ -2388,7 +2388,7 @@ def mock_processor_init(self, **kwargs): kwargs.get("should_export_span") or is_default_export_span ) self._app_root_lock = threading.Lock() - self._app_root_traces = {} + self._span_export_expectation_by_id = {} # For testing, use the appropriate exporter based on setup exporter = kwargs.get("_test_exporter", blocked_exporter) diff --git a/tests/unit/test_span_filter.py b/tests/unit/test_span_filter.py index 94f6ba4f8..cf9965d14 100644 --- a/tests/unit/test_span_filter.py +++ b/tests/unit/test_span_filter.py @@ -3,7 +3,10 @@ from types import SimpleNamespace from typing import Any, Optional +import pytest + from langfuse.span_filter import ( + KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES, is_default_export_span, is_genai_span, is_known_llm_instrumentor, @@ -75,6 +78,56 @@ def test_is_known_llm_instrumentor_prefix_match(): ) +@pytest.mark.parametrize( + "scope_name", + [ + "autogen-core", + "opentelemetry.instrumentation.agno", + "opentelemetry.instrumentation.alephalpha", + "opentelemetry.instrumentation.bedrock", + "opentelemetry.instrumentation.cohere", + "opentelemetry.instrumentation.crewai", + "opentelemetry.instrumentation.google_generativeai", + "opentelemetry.instrumentation.groq", + "opentelemetry.instrumentation.haystack", + "opentelemetry.instrumentation.langchain", + "opentelemetry.instrumentation.llamaindex", + "opentelemetry.instrumentation.mistralai", + "opentelemetry.instrumentation.ollama", + "opentelemetry.instrumentation.openai", + "opentelemetry.instrumentation.openai.v1", + "opentelemetry.instrumentation.openai_agents", + "opentelemetry.instrumentation.openai_v2", + "opentelemetry.instrumentation.replicate", + "opentelemetry.instrumentation.sagemaker", + "opentelemetry.instrumentation.together", + "opentelemetry.instrumentation.transformers", + "opentelemetry.instrumentation.vertexai", + "opentelemetry.instrumentation.voyageai", + "opentelemetry.instrumentation.watsonx", + "opentelemetry.instrumentation.writer", + "pydantic-ai", + ], +) +def test_is_known_llm_instrumentor_researched_scope_names(scope_name): + """Return true for researched OpenTelemetry LLM instrumentation scopes.""" + assert is_known_llm_instrumentor(_make_span(scope_name=scope_name)) is True + + +def test_known_llm_instrumentor_does_not_include_vector_store_only_scopes(): + """Keep vector DB-only instrumentors out of the default LLM scope allowlist.""" + vector_store_scopes = { + "opentelemetry.instrumentation.chromadb", + "opentelemetry.instrumentation.lancedb", + "opentelemetry.instrumentation.milvus", + "opentelemetry.instrumentation.pinecone", + "opentelemetry.instrumentation.qdrant", + "opentelemetry.instrumentation.weaviate", + } + + assert vector_store_scopes.isdisjoint(KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES) + + def test_is_known_llm_instrumentor_unknown(): """Return false for unknown instrumentation scopes.""" assert is_known_llm_instrumentor(_make_span(scope_name="unknown.scope")) is False From e1025bc46fc7339c16c82c569121bbcccf32cc68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 09:58:11 +0200 Subject: [PATCH 277/296] chore(deps): bump zizmorcore/zizmor-action from 0.5.3 to 0.5.5 in the github-actions group (#1665) chore(deps): bump zizmorcore/zizmor-action in the github-actions group Bumps the github-actions group with 1 update: [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action). Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.5 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/b1d7e1fb5de872772f31590499237e7cce841e8e...a16621b09c6db4281f81a93cb393b05dcd7b7165) --- updated-dependencies: - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 03d663f9d..9ffd07d98 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5 with: # Using false as a code scanning ruleset would block the release # workflow which creates a new commit and pushes directly to main. From 77d284b7085b94cd6e0104d65521dc9041e3faa3 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Thu, 21 May 2026 11:06:03 +0200 Subject: [PATCH 278/296] ci: adjust zizmor advanced security handling (#1666) --- .github/workflows/zizmor.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 9ffd07d98..0be663232 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -16,10 +16,10 @@ permissions: {} jobs: zizmor: name: Check GitHub Actions security - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: contents: read + security-events: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -28,7 +28,5 @@ jobs: - name: Run zizmor uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5 with: - # Using false as a code scanning ruleset would block the release - # workflow which creates a new commit and pushes directly to main. - advanced-security: false - min-severity: medium + advanced-security: ${{ github.event_name == 'push' && 'true' || 'false' }} + min-severity: low From 6521e909b27758195fb400f967074466568fb2d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 09:43:43 +0200 Subject: [PATCH 279/296] chore(deps): bump github/codeql-action from 4.35.4 to 4.35.5 in the github-actions group (#1668) chore(deps): bump github/codeql-action in the github-actions group Bumps the github-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.35.4 to 4.35.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/68bde559dea0fdcac2102bfdf6230c5f70eb485e...9e0d7b8d25671d64c341c19c0152d693099fb5ba) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7081242de..fa9b6ddb6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,7 +61,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 + uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -89,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 + uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: category: "/language:${{matrix.language}}" From a462fde972f98f890ee09d1fb18635097c6b161c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 12:45:15 +0200 Subject: [PATCH 280/296] chore(deps): bump idna from 3.11 to 3.15 (#1660) Bumps [idna](https://github.com/kjd/idna) from 3.11 to 3.15. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](https://github.com/kjd/idna/compare/v3.11...v3.15) --- updated-dependencies: - dependency-name: idna dependency-version: '3.15' dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 00695f204..17971bf8d 100644 --- a/uv.lock +++ b/uv.lock @@ -320,11 +320,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] From 2263507f1d7af9d8c683f892e37c9d63a638e9b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 12:47:25 +0200 Subject: [PATCH 281/296] chore(deps): bump urllib3 from 2.6.3 to 2.7.0 (#1669) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 17971bf8d..e91c6fabc 100644 --- a/uv.lock +++ b/uv.lock @@ -2105,11 +2105,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] From 4a4bb46507ea631dd5aa9ef75026afddeb0350ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Flis?= Date: Sun, 24 May 2026 13:58:39 +0200 Subject: [PATCH 282/296] fix(serializer): add depth limit to EventSerializer to prevent hangs on complex objects (#1577) * fix: add depth limit to EventSerializer to prevent hangs on complex objects EventSerializer.default() recursively traverses __dict__ and __slots__ of arbitrary objects without a depth limit. When @observe() captures function arguments containing objects like google.genai.Client (which hold aiohttp sessions, connection pools, and threading locks), json.dumps blocks indefinitely on the second invocation. Add a _MAX_DEPTH=20 counter that returns a placeholder when exceeded, preventing infinite recursion into complex object graphs while preserving all existing serialization behavior. * test: tighten slots depth assertion to reflect double depth-counting * fix: claude comments --- langfuse/_utils/serializer.py | 53 +++++++++------ tests/unit/test_serializer.py | 124 ++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 19 deletions(-) diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index c2dad3312..24c19aa12 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -36,11 +36,21 @@ class Serializable: # type: ignore class EventSerializer(JSONEncoder): + _MAX_DEPTH = 20 + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.seen: set[int] = set() # Track seen objects to detect circular references + self._depth = 0 def default(self, obj: Any) -> Any: + self._depth += 1 + try: + return self._default_inner(obj) + finally: + self._depth -= 1 + + def _default_inner(self, obj: Any) -> Any: try: if isinstance(obj, (datetime)): # Timezone-awareness check @@ -82,9 +92,6 @@ def default(self, obj: Any) -> Any: if isinstance(obj, Queue): return type(obj).__name__ - if is_dataclass(obj): - return asdict(obj) # type: ignore - if isinstance(obj, UUID): return str(obj) @@ -97,22 +104,9 @@ def default(self, obj: Any) -> Any: if isinstance(obj, (date)): return obj.isoformat() - if isinstance(obj, BaseModel): - obj.model_rebuild() - - # For LlamaIndex models, we need to rebuild the raw model as well if they include OpenAI models - if isinstance(raw := getattr(obj, "raw", None), BaseModel): - raw.model_rebuild() - - return obj.model_dump() - if isinstance(obj, Path): return str(obj) - # if langchain is not available, the Serializable type is NoneType - if Serializable is not type(None) and isinstance(obj, Serializable): # type: ignore - return obj.to_json() - # 64-bit integers might overflow the JavaScript safe integer range. # Since Node.js is run on the server that handles the serialized value, # we need to ensure that integers outside the safe range are converted to strings. @@ -123,6 +117,25 @@ def default(self, obj: Any) -> Any: if isinstance(obj, (str, float, type(None))): return obj + if self._depth >= self._MAX_DEPTH: + return f"<{type(obj).__name__}>" + + if is_dataclass(obj): + return asdict(obj) # type: ignore + + if isinstance(obj, BaseModel): + obj.model_rebuild() + + # For LlamaIndex models, we need to rebuild the raw model as well if they include OpenAI models + if isinstance(raw := getattr(obj, "raw", None), BaseModel): + raw.model_rebuild() + + return obj.model_dump() + + # if langchain is not available, the Serializable type is NoneType + if Serializable is not type(None) and isinstance(obj, Serializable): # type: ignore + return obj.to_json() + if isinstance(obj, (tuple, set, frozenset)): return list(obj) @@ -138,9 +151,10 @@ def default(self, obj: Any) -> Any: return [self.default(item) for item in obj] if hasattr(obj, "__slots__"): - return self.default( - {slot: getattr(obj, slot, None) for slot in obj.__slots__} - ) + return { + slot: self.default(getattr(obj, slot, None)) + for slot in obj.__slots__ + } elif hasattr(obj, "__dict__"): obj_id = id(obj) @@ -167,6 +181,7 @@ def default(self, obj: Any) -> Any: def encode(self, obj: Any) -> str: self.seen.clear() # Clear seen objects before each encode call + self._depth = 0 try: return super().encode(self.default(obj)) diff --git a/tests/unit/test_serializer.py b/tests/unit/test_serializer.py index 4faf7019b..ce9462e04 100644 --- a/tests/unit/test_serializer.py +++ b/tests/unit/test_serializer.py @@ -6,6 +6,7 @@ from pathlib import Path from uuid import UUID +import pytest from pydantic import BaseModel from langfuse._utils.serializer import ( @@ -174,3 +175,126 @@ def __init__(self): obj = SlotClass() serializer = EventSerializer() assert json.loads(serializer.encode(obj)) == {"field": "value"} + + +def test_deeply_nested_object_does_not_hang(): + class Inner: + def __init__(self): + self.lock = threading.Lock() + self.value = "deep" + + class Connection: + def __init__(self): + self._inner = Inner() + self._pool = [Inner() for _ in range(3)] + + class Client: + def __init__(self): + self._connection = Connection() + self._config = {"key": "value"} + + class Platform: + def __init__(self): + self._client = Client() + + obj = {"args": (Platform(),), "kwargs": {}} + serializer = EventSerializer() + result = serializer.encode(obj) + + # Must complete without hanging and produce valid JSON + parsed = json.loads(result) + assert "args" in parsed + + +def test_max_depth_returns_type_name(): + class Level: + def __init__(self, child=None): + self.child = child + + # Build a chain deeper than _MAX_DEPTH + obj = None + for _ in range(EventSerializer._MAX_DEPTH + 10): + obj = Level(child=obj) + + serializer = EventSerializer() + result = json.loads(serializer.encode(obj)) + + # Walk down the chain — at some point it should be truncated to "Level" + node = result + found_truncation = False + while isinstance(node, dict) and "child" in node: + if node["child"] == "Level" or node["child"] == "": + found_truncation = True + break + node = node["child"] + + assert found_truncation, "Expected depth limit to truncate deep nesting" + + +def test_deeply_nested_slots_object_is_truncated(): + class SlotLevel: + __slots__ = ["child"] + + def __init__(self, child=None): + self.child = child + + obj = None + for _ in range(EventSerializer._MAX_DEPTH + 10): + obj = SlotLevel(child=obj) + + serializer = EventSerializer() + result = json.loads(serializer.encode(obj)) + + # Walk the nested structure and verify it terminates + node = result + depth = 0 + while isinstance(node, dict): + depth += 1 + if "child" in node: + node = node["child"] + else: + break + + assert EventSerializer._MAX_DEPTH - 2 <= depth <= EventSerializer._MAX_DEPTH + 2, ( + f"Nesting depth {depth} not near _MAX_DEPTH ({EventSerializer._MAX_DEPTH}) — " + "serializer truncated too early or too late" + ) + + +def test_deeply_nested_dict_preserves_keys_at_depth_boundary(monkeypatch): + monkeypatch.setattr(EventSerializer, "_MAX_DEPTH", 3) + + input_obj = {"a": {"b": {"c": "leaf"}}} + expected = {"a": {"b": ""}} + + serializer = EventSerializer() + result = json.loads(serializer.encode(input_obj)) + + assert result == expected + + +class _Color(Enum): + RED = "red" + NUMERIC = 7 + + +@pytest.mark.parametrize( + "input_obj, expected", + [ + ( + {datetime(2024, 1, 1, tzinfo=timezone.utc): "v"}, + {"2024-01-01T00:00:00Z": "v"}, + ), + ( + {UUID("12345678-1234-5678-1234-567812345678"): "v"}, + {"12345678-1234-5678-1234-567812345678": "v"}, + ), + ({_Color.RED: "v"}, {"red": "v"}), + ({_Color.NUMERIC: "v"}, {"7": "v"}), + ], + ids=["datetime", "uuid", "enum_str_value", "enum_int_value"], +) +def test_dict_with_non_string_keys_is_serialized(input_obj, expected): + result = json.loads(EventSerializer().encode(input_obj)) + + assert result == expected From 2d0941a12498c6bd59ee9a8fc6fa5bb362461e1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 10:03:55 +0200 Subject: [PATCH 283/296] chore(deps): bump zizmorcore/zizmor-action from 0.5.5 to 0.5.6 in the github-actions group (#1672) chore(deps): bump zizmorcore/zizmor-action in the github-actions group Bumps the github-actions group with 1 update: [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action). Updates `zizmorcore/zizmor-action` from 0.5.5 to 0.5.6 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/a16621b09c6db4281f81a93cb393b05dcd7b7165...5f14fd08f7cf1cb1609c1e344975f152c7ee938d) --- updated-dependencies: - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 0be663232..60e1b2952 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5 + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 with: advanced-security: ${{ github.event_name == 'push' && 'true' || 'false' }} min-severity: low From f9c765bc0dc3419ad7f0977c45fa4e70bee71252 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 27 May 2026 18:50:56 +0000 Subject: [PATCH 284/296] chore: release v4.7.0 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e37c4215f..e37912fbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.6.1" +version = "4.7.0" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index e91c6fabc..83581a848 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-05-20T18:50:51.815107415Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.6.1" +version = "4.7.0" source = { editable = "." } dependencies = [ { name = "backoff" }, From 062b93586bfe0f648cc10c42efdaf994bc2f6472 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 28 May 2026 17:04:59 +0200 Subject: [PATCH 285/296] feat(api): update API spec from langfuse/langfuse 41f5847 (#1677) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 12 + .../api/blob_storage_integrations/__init__.py | 6 + .../api/blob_storage_integrations/client.py | 46 +- .../blob_storage_integrations/raw_client.py | 42 + .../types/__init__.py | 6 + .../types/blob_storage_export_field_group.py | 62 + .../types/blob_storage_export_frequency.py | 4 + .../types/blob_storage_export_source.py | 35 + .../blob_storage_integration_response.py | 13 + .../types/blob_storage_sync_status.py | 4 +- ...create_blob_storage_integration_request.py | 25 + langfuse/api/client.py | 19 + langfuse/api/commons/types/observation_v2.py | 47 +- langfuse/api/legacy/__init__.py | 4 +- langfuse/api/legacy/score_v1/__init__.py | 5 +- langfuse/api/legacy/score_v1/client.py | 11 + langfuse/api/legacy/score_v1/raw_client.py | 11 + .../api/legacy/score_v1/types/__init__.py | 4 +- .../score_v1/types/create_score_request.py | 6 + .../score_v1/types/create_score_source.py | 28 + langfuse/api/llm_connections/__init__.py | 3 + langfuse/api/llm_connections/client.py | 81 + langfuse/api/llm_connections/raw_client.py | 202 ++ .../api/llm_connections/types/__init__.py | 3 + .../types/delete_llm_connection_response.py | 14 + langfuse/api/observations/client.py | 38 +- langfuse/api/observations/raw_client.py | 38 +- langfuse/api/score_configs/client.py | 6 +- langfuse/api/score_configs/raw_client.py | 6 +- .../types/create_score_config_request.py | 6 +- .../types/update_score_config_request.py | 2 +- langfuse/api/scores/client.py | 4 +- langfuse/api/scores/raw_client.py | 4 +- langfuse/api/unstable/__init__.py | 267 ++ langfuse/api/unstable/client.py | 91 + langfuse/api/unstable/commons/__init__.py | 187 ++ .../api/unstable/commons/types/__init__.py | 211 ++ .../array_options_evaluation_rule_filter.py | 26 + .../types/boolean_evaluation_rule_filter.py | 21 + ...category_options_evaluation_rule_filter.py | 26 + .../types/date_time_evaluation_rule_filter.py | 29 + ...tion_rule_array_options_filter_operator.py | 26 + ...evaluation_rule_boolean_filter_operator.py | 22 + .../commons/types/evaluation_rule_filter.py | 740 ++++++ .../commons/types/evaluation_rule_mapping.py | 74 + .../types/evaluation_rule_mapping_source.py | 51 + .../evaluation_rule_null_filter_operator.py | 22 + .../evaluation_rule_number_filter_operator.py | 34 + ...evaluation_rule_options_filter_operator.py | 22 + .../commons/types/evaluation_rule_status.py | 34 + .../evaluation_rule_string_filter_operator.py | 34 + .../commons/types/evaluation_rule_target.py | 33 + .../commons/types/evaluator_model_config.py | 46 + .../types/evaluator_output_data_type.py | 35 + .../types/evaluator_output_definition.py | 161 ++ .../evaluator_output_field_definition.py | 17 + .../unstable/commons/types/evaluator_scope.py | 29 + .../unstable/commons/types/evaluator_type.py | 21 + .../types/null_evaluation_rule_filter.py | 24 + .../types/number_evaluation_rule_filter.py | 21 + .../number_object_evaluation_rule_filter.py | 26 + ...lic_boolean_evaluator_output_definition.py | 26 + ...categorical_evaluator_output_definition.py | 29 + ...rical_evaluator_output_score_definition.py | 20 + .../public_evaluator_output_definition.py | 167 ++ ...lic_numeric_evaluator_output_definition.py | 26 + .../types/string_evaluation_rule_filter.py | 21 + .../string_object_evaluation_rule_filter.py | 26 + .../string_options_evaluation_rule_filter.py | 24 + langfuse/api/unstable/errors/__init__.py | 84 + .../api/unstable/errors/errors/__init__.py | 68 + .../errors/errors/access_denied_error.py | 15 + .../errors/errors/bad_request_error.py | 15 + .../unstable/errors/errors/conflict_error.py | 15 + .../errors/errors/internal_server_error.py | 15 + .../errors/errors/method_not_allowed_error.py | 15 + .../unstable/errors/errors/not_found_error.py | 15 + .../errors/errors/too_many_requests_error.py | 15 + .../errors/errors/unauthorized_error.py | 15 + .../errors/unprocessable_content_error.py | 15 + .../api/unstable/errors/types/__init__.py | 53 + .../unstable/errors/types/public_api_error.py | 58 + .../errors/types/public_api_error_code.py | 93 + .../errors/types/public_api_error_details.py | 114 + .../types/public_api_validation_issue.py | 34 + .../api/unstable/evaluation_rules/__init__.py | 64 + .../api/unstable/evaluation_rules/client.py | 859 +++++++ .../unstable/evaluation_rules/raw_client.py | 2271 +++++++++++++++++ .../evaluation_rules/types/__init__.py | 62 + .../types/create_evaluation_rule_request.py | 75 + .../types/delete_evaluation_rule_response.py | 21 + .../evaluation_rules/types/evaluation_rule.py | 172 ++ .../types/evaluation_rule_evaluator.py | 35 + .../evaluation_rule_evaluator_reference.py | 29 + .../types/evaluation_rules.py | 28 + .../types/update_evaluation_rule_request.py | 74 + langfuse/api/unstable/evaluators/__init__.py | 44 + langfuse/api/unstable/evaluators/client.py | 458 ++++ .../api/unstable/evaluators/raw_client.py | 1278 ++++++++++ .../api/unstable/evaluators/types/__init__.py | 46 + .../types/create_evaluator_request.py | 50 + .../unstable/evaluators/types/evaluator.py | 118 + .../unstable/evaluators/types/evaluators.py | 17 + langfuse/api/unstable/raw_client.py | 13 + 104 files changed, 9784 insertions(+), 35 deletions(-) create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_export_field_group.py create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_export_source.py create mode 100644 langfuse/api/legacy/score_v1/types/create_score_source.py create mode 100644 langfuse/api/llm_connections/types/delete_llm_connection_response.py create mode 100644 langfuse/api/unstable/__init__.py create mode 100644 langfuse/api/unstable/client.py create mode 100644 langfuse/api/unstable/commons/__init__.py create mode 100644 langfuse/api/unstable/commons/types/__init__.py create mode 100644 langfuse/api/unstable/commons/types/array_options_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/boolean_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/category_options_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/date_time_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_array_options_filter_operator.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_boolean_filter_operator.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_mapping.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_mapping_source.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_null_filter_operator.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_number_filter_operator.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_options_filter_operator.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_status.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_string_filter_operator.py create mode 100644 langfuse/api/unstable/commons/types/evaluation_rule_target.py create mode 100644 langfuse/api/unstable/commons/types/evaluator_model_config.py create mode 100644 langfuse/api/unstable/commons/types/evaluator_output_data_type.py create mode 100644 langfuse/api/unstable/commons/types/evaluator_output_definition.py create mode 100644 langfuse/api/unstable/commons/types/evaluator_output_field_definition.py create mode 100644 langfuse/api/unstable/commons/types/evaluator_scope.py create mode 100644 langfuse/api/unstable/commons/types/evaluator_type.py create mode 100644 langfuse/api/unstable/commons/types/null_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/number_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/number_object_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/public_boolean_evaluator_output_definition.py create mode 100644 langfuse/api/unstable/commons/types/public_categorical_evaluator_output_definition.py create mode 100644 langfuse/api/unstable/commons/types/public_categorical_evaluator_output_score_definition.py create mode 100644 langfuse/api/unstable/commons/types/public_evaluator_output_definition.py create mode 100644 langfuse/api/unstable/commons/types/public_numeric_evaluator_output_definition.py create mode 100644 langfuse/api/unstable/commons/types/string_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/string_object_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/commons/types/string_options_evaluation_rule_filter.py create mode 100644 langfuse/api/unstable/errors/__init__.py create mode 100644 langfuse/api/unstable/errors/errors/__init__.py create mode 100644 langfuse/api/unstable/errors/errors/access_denied_error.py create mode 100644 langfuse/api/unstable/errors/errors/bad_request_error.py create mode 100644 langfuse/api/unstable/errors/errors/conflict_error.py create mode 100644 langfuse/api/unstable/errors/errors/internal_server_error.py create mode 100644 langfuse/api/unstable/errors/errors/method_not_allowed_error.py create mode 100644 langfuse/api/unstable/errors/errors/not_found_error.py create mode 100644 langfuse/api/unstable/errors/errors/too_many_requests_error.py create mode 100644 langfuse/api/unstable/errors/errors/unauthorized_error.py create mode 100644 langfuse/api/unstable/errors/errors/unprocessable_content_error.py create mode 100644 langfuse/api/unstable/errors/types/__init__.py create mode 100644 langfuse/api/unstable/errors/types/public_api_error.py create mode 100644 langfuse/api/unstable/errors/types/public_api_error_code.py create mode 100644 langfuse/api/unstable/errors/types/public_api_error_details.py create mode 100644 langfuse/api/unstable/errors/types/public_api_validation_issue.py create mode 100644 langfuse/api/unstable/evaluation_rules/__init__.py create mode 100644 langfuse/api/unstable/evaluation_rules/client.py create mode 100644 langfuse/api/unstable/evaluation_rules/raw_client.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/__init__.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/delete_evaluation_rule_response.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/evaluation_rules.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py create mode 100644 langfuse/api/unstable/evaluators/__init__.py create mode 100644 langfuse/api/unstable/evaluators/client.py create mode 100644 langfuse/api/unstable/evaluators/raw_client.py create mode 100644 langfuse/api/unstable/evaluators/types/__init__.py create mode 100644 langfuse/api/unstable/evaluators/types/create_evaluator_request.py create mode 100644 langfuse/api/unstable/evaluators/types/evaluator.py create mode 100644 langfuse/api/unstable/evaluators/types/evaluators.py create mode 100644 langfuse/api/unstable/raw_client.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index aa103cf12..0e036263a 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -32,6 +32,7 @@ scores, sessions, trace, + unstable, utils, ) from .annotation_queues import ( @@ -50,8 +51,10 @@ UpdateAnnotationQueueItemRequest, ) from .blob_storage_integrations import ( + BlobStorageExportFieldGroup, BlobStorageExportFrequency, BlobStorageExportMode, + BlobStorageExportSource, BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationResponse, @@ -186,6 +189,7 @@ UsageDetails, ) from .llm_connections import ( + DeleteLlmConnectionResponse, LlmAdapter, LlmConnection, PaginatedLlmConnections, @@ -312,8 +316,10 @@ "BasePrompt": ".prompts", "BaseScore": ".commons", "BaseScoreV1": ".commons", + "BlobStorageExportFieldGroup": ".blob_storage_integrations", "BlobStorageExportFrequency": ".blob_storage_integrations", "BlobStorageExportMode": ".blob_storage_integrations", + "BlobStorageExportSource": ".blob_storage_integrations", "BlobStorageIntegrationDeletionResponse": ".blob_storage_integrations", "BlobStorageIntegrationFileType": ".blob_storage_integrations", "BlobStorageIntegrationResponse": ".blob_storage_integrations", @@ -368,6 +374,7 @@ "DeleteAnnotationQueueItemResponse": ".annotation_queues", "DeleteDatasetItemResponse": ".dataset_items", "DeleteDatasetRunResponse": ".datasets", + "DeleteLlmConnectionResponse": ".llm_connections", "DeleteMembershipRequest": ".organizations", "DeleteTraceResponse": ".trace", "EmptyResponse": ".scim", @@ -557,6 +564,7 @@ "scores": ".scores", "sessions": ".sessions", "trace": ".trace", + "unstable": ".unstable", "utils": ".utils", } @@ -605,8 +613,10 @@ def __dir__(): "BasePrompt", "BaseScore", "BaseScoreV1", + "BlobStorageExportFieldGroup", "BlobStorageExportFrequency", "BlobStorageExportMode", + "BlobStorageExportSource", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", @@ -661,6 +671,7 @@ def __dir__(): "DeleteAnnotationQueueItemResponse", "DeleteDatasetItemResponse", "DeleteDatasetRunResponse", + "DeleteLlmConnectionResponse", "DeleteMembershipRequest", "DeleteTraceResponse", "EmptyResponse", @@ -850,5 +861,6 @@ def __dir__(): "scores", "sessions", "trace", + "unstable", "utils", ] diff --git a/langfuse/api/blob_storage_integrations/__init__.py b/langfuse/api/blob_storage_integrations/__init__.py index 266be2a6c..d92046ef2 100644 --- a/langfuse/api/blob_storage_integrations/__init__.py +++ b/langfuse/api/blob_storage_integrations/__init__.py @@ -7,8 +7,10 @@ if typing.TYPE_CHECKING: from .types import ( + BlobStorageExportFieldGroup, BlobStorageExportFrequency, BlobStorageExportMode, + BlobStorageExportSource, BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationResponse, @@ -19,8 +21,10 @@ CreateBlobStorageIntegrationRequest, ) _dynamic_imports: typing.Dict[str, str] = { + "BlobStorageExportFieldGroup": ".types", "BlobStorageExportFrequency": ".types", "BlobStorageExportMode": ".types", + "BlobStorageExportSource": ".types", "BlobStorageIntegrationDeletionResponse": ".types", "BlobStorageIntegrationFileType": ".types", "BlobStorageIntegrationResponse": ".types", @@ -60,8 +64,10 @@ def __dir__(): __all__ = [ + "BlobStorageExportFieldGroup", "BlobStorageExportFrequency", "BlobStorageExportMode", + "BlobStorageExportSource", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index 21eeffde3..609e83fd3 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -9,8 +9,10 @@ AsyncRawBlobStorageIntegrationsClient, RawBlobStorageIntegrationsClient, ) +from .types.blob_storage_export_field_group import BlobStorageExportFieldGroup from .types.blob_storage_export_frequency import BlobStorageExportFrequency from .types.blob_storage_export_mode import BlobStorageExportMode +from .types.blob_storage_export_source import BlobStorageExportSource from .types.blob_storage_integration_deletion_response import ( BlobStorageIntegrationDeletionResponse, ) @@ -95,6 +97,10 @@ def upsert_blob_storage_integration( prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, compressed: typing.Optional[bool] = OMIT, + export_source: typing.Optional[BlobStorageExportSource] = OMIT, + export_field_groups: typing.Optional[ + typing.Sequence[BlobStorageExportFieldGroup] + ] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> BlobStorageIntegrationResponse: """ @@ -143,6 +149,20 @@ def upsert_blob_storage_integration( compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + export_source : typing.Optional[BlobStorageExportSource] + Data to export. When omitted on update, the existing value is preserved. When omitted on create: pre-cutoff Cloud projects and self-hosted deployments fall back to `LEGACY_TRACES_OBSERVATIONS`; post-cutoff Cloud projects (created on or after 2026-05-20) auto-default to `OBSERVATIONS_V2`. Required when `exportFieldGroups` is provided. + + **Cloud-only deprecation gate (effective 2026-05-20):** For projects created on or after 2026-05-20 on Langfuse Cloud, `LEGACY_TRACES_OBSERVATIONS` and `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP 400. Omitting `exportSource` on these projects silently defaults to `OBSERVATIONS_V2` rather than the schema column default. Use `OBSERVATIONS_V2` for all new integrations. Projects created before 2026-05-20 and self-hosted deployments are unaffected. + + export_field_groups : typing.Optional[typing.Sequence[BlobStorageExportFieldGroup]] + Field groups to include in each exported row. + + For exportSource `OBSERVATIONS_V2` or `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: must include `core` if provided. When omitted on create, the column default (all groups) applies. When omitted on update, the existing value is preserved. + + For exportSource `LEGACY_TRACES_OBSERVATIONS`: this field must be omitted or null. Sending an array (including an empty array) returns 400, because that source uses a fixed column set and does not honor field groups. + + `exportFieldGroups` requires `exportSource` to be provided in the same request. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -173,7 +193,7 @@ def upsert_blob_storage_integration( type=BlobStorageIntegrationType.S3, bucket_name="bucketName", region="region", - export_frequency=BlobStorageExportFrequency.HOURLY, + export_frequency=BlobStorageExportFrequency.EVERY20MINUTES, enabled=True, force_path_style=True, file_type=BlobStorageIntegrationFileType.JSON, @@ -196,6 +216,8 @@ def upsert_blob_storage_integration( prefix=prefix, export_start_date=export_start_date, compressed=compressed, + export_source=export_source, + export_field_groups=export_field_groups, request_options=request_options, ) return _response.data @@ -354,6 +376,10 @@ async def upsert_blob_storage_integration( prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, compressed: typing.Optional[bool] = OMIT, + export_source: typing.Optional[BlobStorageExportSource] = OMIT, + export_field_groups: typing.Optional[ + typing.Sequence[BlobStorageExportFieldGroup] + ] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> BlobStorageIntegrationResponse: """ @@ -402,6 +428,20 @@ async def upsert_blob_storage_integration( compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + export_source : typing.Optional[BlobStorageExportSource] + Data to export. When omitted on update, the existing value is preserved. When omitted on create: pre-cutoff Cloud projects and self-hosted deployments fall back to `LEGACY_TRACES_OBSERVATIONS`; post-cutoff Cloud projects (created on or after 2026-05-20) auto-default to `OBSERVATIONS_V2`. Required when `exportFieldGroups` is provided. + + **Cloud-only deprecation gate (effective 2026-05-20):** For projects created on or after 2026-05-20 on Langfuse Cloud, `LEGACY_TRACES_OBSERVATIONS` and `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP 400. Omitting `exportSource` on these projects silently defaults to `OBSERVATIONS_V2` rather than the schema column default. Use `OBSERVATIONS_V2` for all new integrations. Projects created before 2026-05-20 and self-hosted deployments are unaffected. + + export_field_groups : typing.Optional[typing.Sequence[BlobStorageExportFieldGroup]] + Field groups to include in each exported row. + + For exportSource `OBSERVATIONS_V2` or `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: must include `core` if provided. When omitted on create, the column default (all groups) applies. When omitted on update, the existing value is preserved. + + For exportSource `LEGACY_TRACES_OBSERVATIONS`: this field must be omitted or null. Sending an array (including an empty array) returns 400, because that source uses a fixed column set and does not honor field groups. + + `exportFieldGroups` requires `exportSource` to be provided in the same request. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -437,7 +477,7 @@ async def main() -> None: type=BlobStorageIntegrationType.S3, bucket_name="bucketName", region="region", - export_frequency=BlobStorageExportFrequency.HOURLY, + export_frequency=BlobStorageExportFrequency.EVERY20MINUTES, enabled=True, force_path_style=True, file_type=BlobStorageIntegrationFileType.JSON, @@ -463,6 +503,8 @@ async def main() -> None: prefix=prefix, export_start_date=export_start_date, compressed=compressed, + export_source=export_source, + export_field_groups=export_field_groups, request_options=request_options, ) return _response.data diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 5833ea63e..09e036db6 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -15,8 +15,10 @@ from ..core.jsonable_encoder import jsonable_encoder from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions +from .types.blob_storage_export_field_group import BlobStorageExportFieldGroup from .types.blob_storage_export_frequency import BlobStorageExportFrequency from .types.blob_storage_export_mode import BlobStorageExportMode +from .types.blob_storage_export_source import BlobStorageExportSource from .types.blob_storage_integration_deletion_response import ( BlobStorageIntegrationDeletionResponse, ) @@ -152,6 +154,10 @@ def upsert_blob_storage_integration( prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, compressed: typing.Optional[bool] = OMIT, + export_source: typing.Optional[BlobStorageExportSource] = OMIT, + export_field_groups: typing.Optional[ + typing.Sequence[BlobStorageExportFieldGroup] + ] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[BlobStorageIntegrationResponse]: """ @@ -200,6 +206,20 @@ def upsert_blob_storage_integration( compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + export_source : typing.Optional[BlobStorageExportSource] + Data to export. When omitted on update, the existing value is preserved. When omitted on create: pre-cutoff Cloud projects and self-hosted deployments fall back to `LEGACY_TRACES_OBSERVATIONS`; post-cutoff Cloud projects (created on or after 2026-05-20) auto-default to `OBSERVATIONS_V2`. Required when `exportFieldGroups` is provided. + + **Cloud-only deprecation gate (effective 2026-05-20):** For projects created on or after 2026-05-20 on Langfuse Cloud, `LEGACY_TRACES_OBSERVATIONS` and `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP 400. Omitting `exportSource` on these projects silently defaults to `OBSERVATIONS_V2` rather than the schema column default. Use `OBSERVATIONS_V2` for all new integrations. Projects created before 2026-05-20 and self-hosted deployments are unaffected. + + export_field_groups : typing.Optional[typing.Sequence[BlobStorageExportFieldGroup]] + Field groups to include in each exported row. + + For exportSource `OBSERVATIONS_V2` or `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: must include `core` if provided. When omitted on create, the column default (all groups) applies. When omitted on update, the existing value is preserved. + + For exportSource `LEGACY_TRACES_OBSERVATIONS`: this field must be omitted or null. Sending an array (including an empty array) returns 400, because that source uses a fixed column set and does not honor field groups. + + `exportFieldGroups` requires `exportSource` to be provided in the same request. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -226,6 +246,8 @@ def upsert_blob_storage_integration( "exportMode": export_mode, "exportStartDate": export_start_date, "compressed": compressed, + "exportSource": export_source, + "exportFieldGroups": export_field_groups, }, request_options=request_options, omit=OMIT, @@ -629,6 +651,10 @@ async def upsert_blob_storage_integration( prefix: typing.Optional[str] = OMIT, export_start_date: typing.Optional[dt.datetime] = OMIT, compressed: typing.Optional[bool] = OMIT, + export_source: typing.Optional[BlobStorageExportSource] = OMIT, + export_field_groups: typing.Optional[ + typing.Sequence[BlobStorageExportFieldGroup] + ] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[BlobStorageIntegrationResponse]: """ @@ -677,6 +703,20 @@ async def upsert_blob_storage_integration( compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. + export_source : typing.Optional[BlobStorageExportSource] + Data to export. When omitted on update, the existing value is preserved. When omitted on create: pre-cutoff Cloud projects and self-hosted deployments fall back to `LEGACY_TRACES_OBSERVATIONS`; post-cutoff Cloud projects (created on or after 2026-05-20) auto-default to `OBSERVATIONS_V2`. Required when `exportFieldGroups` is provided. + + **Cloud-only deprecation gate (effective 2026-05-20):** For projects created on or after 2026-05-20 on Langfuse Cloud, `LEGACY_TRACES_OBSERVATIONS` and `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP 400. Omitting `exportSource` on these projects silently defaults to `OBSERVATIONS_V2` rather than the schema column default. Use `OBSERVATIONS_V2` for all new integrations. Projects created before 2026-05-20 and self-hosted deployments are unaffected. + + export_field_groups : typing.Optional[typing.Sequence[BlobStorageExportFieldGroup]] + Field groups to include in each exported row. + + For exportSource `OBSERVATIONS_V2` or `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: must include `core` if provided. When omitted on create, the column default (all groups) applies. When omitted on update, the existing value is preserved. + + For exportSource `LEGACY_TRACES_OBSERVATIONS`: this field must be omitted or null. Sending an array (including an empty array) returns 400, because that source uses a fixed column set and does not honor field groups. + + `exportFieldGroups` requires `exportSource` to be provided in the same request. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -703,6 +743,8 @@ async def upsert_blob_storage_integration( "exportMode": export_mode, "exportStartDate": export_start_date, "compressed": compressed, + "exportSource": export_source, + "exportFieldGroups": export_field_groups, }, request_options=request_options, omit=OMIT, diff --git a/langfuse/api/blob_storage_integrations/types/__init__.py b/langfuse/api/blob_storage_integrations/types/__init__.py index e0fe3e9ff..3a2a0e1ec 100644 --- a/langfuse/api/blob_storage_integrations/types/__init__.py +++ b/langfuse/api/blob_storage_integrations/types/__init__.py @@ -6,8 +6,10 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .blob_storage_export_field_group import BlobStorageExportFieldGroup from .blob_storage_export_frequency import BlobStorageExportFrequency from .blob_storage_export_mode import BlobStorageExportMode + from .blob_storage_export_source import BlobStorageExportSource from .blob_storage_integration_deletion_response import ( BlobStorageIntegrationDeletionResponse, ) @@ -23,8 +25,10 @@ CreateBlobStorageIntegrationRequest, ) _dynamic_imports: typing.Dict[str, str] = { + "BlobStorageExportFieldGroup": ".blob_storage_export_field_group", "BlobStorageExportFrequency": ".blob_storage_export_frequency", "BlobStorageExportMode": ".blob_storage_export_mode", + "BlobStorageExportSource": ".blob_storage_export_source", "BlobStorageIntegrationDeletionResponse": ".blob_storage_integration_deletion_response", "BlobStorageIntegrationFileType": ".blob_storage_integration_file_type", "BlobStorageIntegrationResponse": ".blob_storage_integration_response", @@ -64,8 +68,10 @@ def __dir__(): __all__ = [ + "BlobStorageExportFieldGroup", "BlobStorageExportFrequency", "BlobStorageExportMode", + "BlobStorageExportSource", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationResponse", diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_export_field_group.py b/langfuse/api/blob_storage_integrations/types/blob_storage_export_field_group.py new file mode 100644 index 000000000..c21a9c3bb --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_export_field_group.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageExportFieldGroup(enum.StrEnum): + """ + Field group for the OBSERVATIONS_V2 and LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS export. + """ + + CORE = "core" + BASIC = "basic" + TIME = "time" + IO = "io" + METADATA = "metadata" + MODEL = "model" + USAGE = "usage" + PROMPT = "prompt" + METRICS = "metrics" + TOOLS = "tools" + TRACE_CONTEXT = "trace_context" + + def visit( + self, + core: typing.Callable[[], T_Result], + basic: typing.Callable[[], T_Result], + time: typing.Callable[[], T_Result], + io: typing.Callable[[], T_Result], + metadata: typing.Callable[[], T_Result], + model: typing.Callable[[], T_Result], + usage: typing.Callable[[], T_Result], + prompt: typing.Callable[[], T_Result], + metrics: typing.Callable[[], T_Result], + tools: typing.Callable[[], T_Result], + trace_context: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageExportFieldGroup.CORE: + return core() + if self is BlobStorageExportFieldGroup.BASIC: + return basic() + if self is BlobStorageExportFieldGroup.TIME: + return time() + if self is BlobStorageExportFieldGroup.IO: + return io() + if self is BlobStorageExportFieldGroup.METADATA: + return metadata() + if self is BlobStorageExportFieldGroup.MODEL: + return model() + if self is BlobStorageExportFieldGroup.USAGE: + return usage() + if self is BlobStorageExportFieldGroup.PROMPT: + return prompt() + if self is BlobStorageExportFieldGroup.METRICS: + return metrics() + if self is BlobStorageExportFieldGroup.TOOLS: + return tools() + if self is BlobStorageExportFieldGroup.TRACE_CONTEXT: + return trace_context() diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py b/langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py index bcc7fc6d5..4799ecefb 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_export_frequency.py @@ -8,16 +8,20 @@ class BlobStorageExportFrequency(enum.StrEnum): + EVERY20MINUTES = "every_20_minutes" HOURLY = "hourly" DAILY = "daily" WEEKLY = "weekly" def visit( self, + every20minutes: typing.Callable[[], T_Result], hourly: typing.Callable[[], T_Result], daily: typing.Callable[[], T_Result], weekly: typing.Callable[[], T_Result], ) -> T_Result: + if self is BlobStorageExportFrequency.EVERY20MINUTES: + return every20minutes() if self is BlobStorageExportFrequency.HOURLY: return hourly() if self is BlobStorageExportFrequency.DAILY: diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_export_source.py b/langfuse/api/blob_storage_integrations/types/blob_storage_export_source.py new file mode 100644 index 000000000..1451473b4 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_export_source.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageExportSource(enum.StrEnum): + """ + What data the integration exports. + - `LEGACY_TRACES_OBSERVATIONS`: traces, observations, and scores tables with a fixed column set. The `exportFieldGroups` field is not applicable. + - `OBSERVATIONS_V2`: same data model as the `/api/public/v2/observations` endpoint, plus scores. Columns are controlled by `exportFieldGroups`. + - `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: both sets. For the `OBSERVATIONS_V2` portion, columns are controlled by `exportFieldGroups`. + + **Note:** `OBSERVATIONS_V2` and the enriched-observations portion of `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` rely on the enriched observations table (Langfuse Fast Preview / v4), which is currently available on Langfuse Cloud only. See https://langfuse.com/docs/v4. + """ + + LEGACY_TRACES_OBSERVATIONS = "LEGACY_TRACES_OBSERVATIONS" + OBSERVATIONS_V2 = "OBSERVATIONS_V2" + LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS = "LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS" + + def visit( + self, + legacy_traces_observations: typing.Callable[[], T_Result], + observations_v2: typing.Callable[[], T_Result], + legacy_traces_and_enriched_observations: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageExportSource.LEGACY_TRACES_OBSERVATIONS: + return legacy_traces_observations() + if self is BlobStorageExportSource.OBSERVATIONS_V2: + return observations_v2() + if self is BlobStorageExportSource.LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS: + return legacy_traces_and_enriched_observations() diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py index b3630297b..e2b5921a0 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py @@ -7,8 +7,10 @@ import typing_extensions from ...core.pydantic_utilities import UniversalBaseModel from ...core.serialization import FieldMetadata +from .blob_storage_export_field_group import BlobStorageExportFieldGroup from .blob_storage_export_frequency import BlobStorageExportFrequency from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_export_source import BlobStorageExportSource from .blob_storage_integration_file_type import BlobStorageIntegrationFileType from .blob_storage_integration_type import BlobStorageIntegrationType @@ -41,6 +43,17 @@ class BlobStorageIntegrationResponse(UniversalBaseModel): typing.Optional[dt.datetime], FieldMetadata(alias="exportStartDate") ] = None compressed: bool + export_source: typing_extensions.Annotated[ + BlobStorageExportSource, FieldMetadata(alias="exportSource") + ] + export_field_groups: typing_extensions.Annotated[ + typing.Optional[typing.List[BlobStorageExportFieldGroup]], + FieldMetadata(alias="exportFieldGroups"), + ] = pydantic.Field(default=None) + """ + Field groups included in each exported row for `OBSERVATIONS_V2` / `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` sources. Always `null` when exportSource is `LEGACY_TRACES_OBSERVATIONS` (the field does not apply to that source; any legacy DB value is hidden from the public surface). + """ + next_sync_at: typing_extensions.Annotated[ typing.Optional[dt.datetime], FieldMetadata(alias="nextSyncAt") ] = None diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py index 254e06645..559e41450 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py @@ -17,8 +17,8 @@ class BlobStorageSyncStatus(enum.StrEnum): - `up_to_date` — all available data has been exported; next export is scheduled for the future **ETL usage**: poll this endpoint and check for `up_to_date` status. Compare `lastSyncAt` against your - ETL bookmark to determine if new data is available. Note that exports run with a 30-minute lag buffer, - so `lastSyncAt` will always be at least 30 minutes behind real-time. + ETL bookmark to determine if new data is available. Note that exports run with a 20-minute lag buffer, + so `lastSyncAt` will always be at least 20 minutes behind real-time. """ IDLE = "idle" diff --git a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py index ada6e432b..89c9bca4a 100644 --- a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +++ b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py @@ -7,8 +7,10 @@ import typing_extensions from ...core.pydantic_utilities import UniversalBaseModel from ...core.serialization import FieldMetadata +from .blob_storage_export_field_group import BlobStorageExportFieldGroup from .blob_storage_export_frequency import BlobStorageExportFrequency from .blob_storage_export_mode import BlobStorageExportMode +from .blob_storage_export_source import BlobStorageExportSource from .blob_storage_integration_file_type import BlobStorageIntegrationFileType from .blob_storage_integration_type import BlobStorageIntegrationType @@ -91,6 +93,29 @@ class CreateBlobStorageIntegrationRequest(UniversalBaseModel): Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. """ + export_source: typing_extensions.Annotated[ + typing.Optional[BlobStorageExportSource], FieldMetadata(alias="exportSource") + ] = pydantic.Field(default=None) + """ + Data to export. When omitted on update, the existing value is preserved. When omitted on create: pre-cutoff Cloud projects and self-hosted deployments fall back to `LEGACY_TRACES_OBSERVATIONS`; post-cutoff Cloud projects (created on or after 2026-05-20) auto-default to `OBSERVATIONS_V2`. Required when `exportFieldGroups` is provided. + + **Cloud-only deprecation gate (effective 2026-05-20):** For projects created on or after 2026-05-20 on Langfuse Cloud, `LEGACY_TRACES_OBSERVATIONS` and `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP 400. Omitting `exportSource` on these projects silently defaults to `OBSERVATIONS_V2` rather than the schema column default. Use `OBSERVATIONS_V2` for all new integrations. Projects created before 2026-05-20 and self-hosted deployments are unaffected. + """ + + export_field_groups: typing_extensions.Annotated[ + typing.Optional[typing.List[BlobStorageExportFieldGroup]], + FieldMetadata(alias="exportFieldGroups"), + ] = pydantic.Field(default=None) + """ + Field groups to include in each exported row. + + For exportSource `OBSERVATIONS_V2` or `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: must include `core` if provided. When omitted on create, the column default (all groups) applies. When omitted on update, the existing value is preserved. + + For exportSource `LEGACY_TRACES_OBSERVATIONS`: this field must be omitted or null. Sending an array (including an empty array) returns 400, because that source uses a fixed column set and does not honor field groups. + + `exportFieldGroups` requires `exportSource` to be provided in the same request. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) diff --git a/langfuse/api/client.py b/langfuse/api/client.py index 3f656cdcd..c0413704b 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -41,6 +41,7 @@ from .scores.client import AsyncScoresClient, ScoresClient from .sessions.client import AsyncSessionsClient, SessionsClient from .trace.client import AsyncTraceClient, TraceClient + from .unstable.client import AsyncUnstableClient, UnstableClient class LangfuseAPI: @@ -147,6 +148,7 @@ def __init__( self._scores: typing.Optional[ScoresClient] = None self._sessions: typing.Optional[SessionsClient] = None self._trace: typing.Optional[TraceClient] = None + self._unstable: typing.Optional[UnstableClient] = None @property def annotation_queues(self): @@ -358,6 +360,14 @@ def trace(self): self._trace = TraceClient(client_wrapper=self._client_wrapper) return self._trace + @property + def unstable(self): + if self._unstable is None: + from .unstable.client import UnstableClient # noqa: E402 + + self._unstable = UnstableClient(client_wrapper=self._client_wrapper) + return self._unstable + class AsyncLangfuseAPI: """ @@ -463,6 +473,7 @@ def __init__( self._scores: typing.Optional[AsyncScoresClient] = None self._sessions: typing.Optional[AsyncSessionsClient] = None self._trace: typing.Optional[AsyncTraceClient] = None + self._unstable: typing.Optional[AsyncUnstableClient] = None @property def annotation_queues(self): @@ -677,3 +688,11 @@ def trace(self): self._trace = AsyncTraceClient(client_wrapper=self._client_wrapper) return self._trace + + @property + def unstable(self): + if self._unstable is None: + from .unstable.client import AsyncUnstableClient # noqa: E402 + + self._unstable = AsyncUnstableClient(client_wrapper=self._client_wrapper) + return self._unstable diff --git a/langfuse/api/commons/types/observation_v2.py b/langfuse/api/commons/types/observation_v2.py index 149dfb422..08c1604cf 100644 --- a/langfuse/api/commons/types/observation_v2.py +++ b/langfuse/api/commons/types/observation_v2.py @@ -190,6 +190,13 @@ class ObservationV2(UniversalBaseModel): The total cost of the observation in USD """ + usage_pricing_tier_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="usagePricingTierName") + ] = pydantic.Field(default=None) + """ + The name of the pricing tier applied to this observation's usage costs + """ + prompt_id: typing_extensions.Annotated[ typing.Optional[str], FieldMetadata(alias="promptId") ] = pydantic.Field(default=None) @@ -227,7 +234,45 @@ class ObservationV2(UniversalBaseModel): typing.Optional[str], FieldMetadata(alias="modelId") ] = pydantic.Field(default=None) """ - The matched model ID + The matched model ID. Null when the `model` field group is not requested. + """ + + input_price: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="inputPrice") + ] = pydantic.Field(default=None) + """ + The input token price (USD per unit) from the matched model, serialized as a decimal string (e.g. "0.0001"). Null when the `model` field group is not requested. + """ + + output_price: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="outputPrice") + ] = pydantic.Field(default=None) + """ + The output token price (USD per unit) from the matched model, serialized as a decimal string (e.g. "0.0001"). Null when the `model` field group is not requested. + """ + + total_price: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="totalPrice") + ] = pydantic.Field(default=None) + """ + The total token price (USD per unit) from the matched model, serialized as a decimal string (e.g. "0.0001"). Null when the `model` field group is not requested. + """ + + trace_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceName") + ] = pydantic.Field(default=None) + """ + The name of the parent trace + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Tags from the parent trace (denormalized onto the observation) + """ + + release: typing.Optional[str] = pydantic.Field(default=None) + """ + The release version of the parent trace """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( diff --git a/langfuse/api/legacy/__init__.py b/langfuse/api/legacy/__init__.py index d91b42c2b..0a67d1c0c 100644 --- a/langfuse/api/legacy/__init__.py +++ b/langfuse/api/legacy/__init__.py @@ -9,10 +9,11 @@ from . import metrics_v1, observations_v1, score_v1 from .metrics_v1 import MetricsResponse from .observations_v1 import Observations, ObservationsViews - from .score_v1 import CreateScoreRequest, CreateScoreResponse + from .score_v1 import CreateScoreRequest, CreateScoreResponse, CreateScoreSource _dynamic_imports: typing.Dict[str, str] = { "CreateScoreRequest": ".score_v1", "CreateScoreResponse": ".score_v1", + "CreateScoreSource": ".score_v1", "MetricsResponse": ".metrics_v1", "Observations": ".observations_v1", "ObservationsViews": ".observations_v1", @@ -52,6 +53,7 @@ def __dir__(): __all__ = [ "CreateScoreRequest", "CreateScoreResponse", + "CreateScoreSource", "MetricsResponse", "Observations", "ObservationsViews", diff --git a/langfuse/api/legacy/score_v1/__init__.py b/langfuse/api/legacy/score_v1/__init__.py index 3d0c7422a..4841a9656 100644 --- a/langfuse/api/legacy/score_v1/__init__.py +++ b/langfuse/api/legacy/score_v1/__init__.py @@ -6,10 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import CreateScoreRequest, CreateScoreResponse + from .types import CreateScoreRequest, CreateScoreResponse, CreateScoreSource _dynamic_imports: typing.Dict[str, str] = { "CreateScoreRequest": ".types", "CreateScoreResponse": ".types", + "CreateScoreSource": ".types", } @@ -40,4 +41,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["CreateScoreRequest", "CreateScoreResponse"] +__all__ = ["CreateScoreRequest", "CreateScoreResponse", "CreateScoreSource"] diff --git a/langfuse/api/legacy/score_v1/client.py b/langfuse/api/legacy/score_v1/client.py index 03ca8b836..60f118747 100644 --- a/langfuse/api/legacy/score_v1/client.py +++ b/langfuse/api/legacy/score_v1/client.py @@ -8,6 +8,7 @@ from ...core.request_options import RequestOptions from .raw_client import AsyncRawScoreV1Client, RawScoreV1Client from .types.create_score_response import CreateScoreResponse +from .types.create_score_source import CreateScoreSource # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -44,6 +45,7 @@ def create( queue_id: typing.Optional[str] = OMIT, data_type: typing.Optional[ScoreDataType] = OMIT, config_id: typing.Optional[str] = OMIT, + source: typing.Optional[CreateScoreSource] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> CreateScoreResponse: """ @@ -82,6 +84,9 @@ def create( config_id : typing.Optional[str] Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + source : typing.Optional[CreateScoreSource] + The source of the score. Defaults to API. Set to ANNOTATION to prefill scores (e.g. from an LLM) for a human reviewer to verify in an annotation queue. When source is ANNOTATION, a configId is required unless dataType is CORRECTION. EVAL is reserved for internal evaluator outputs and is not accepted on this endpoint. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -120,6 +125,7 @@ def create( queue_id=queue_id, data_type=data_type, config_id=config_id, + source=source, request_options=request_options, ) return _response.data @@ -193,6 +199,7 @@ async def create( queue_id: typing.Optional[str] = OMIT, data_type: typing.Optional[ScoreDataType] = OMIT, config_id: typing.Optional[str] = OMIT, + source: typing.Optional[CreateScoreSource] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> CreateScoreResponse: """ @@ -231,6 +238,9 @@ async def create( config_id : typing.Optional[str] Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + source : typing.Optional[CreateScoreSource] + The source of the score. Defaults to API. Set to ANNOTATION to prefill scores (e.g. from an LLM) for a human reviewer to verify in an annotation queue. When source is ANNOTATION, a configId is required unless dataType is CORRECTION. EVAL is reserved for internal evaluator outputs and is not accepted on this endpoint. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -277,6 +287,7 @@ async def main() -> None: queue_id=queue_id, data_type=data_type, config_id=config_id, + source=source, request_options=request_options, ) return _response.data diff --git a/langfuse/api/legacy/score_v1/raw_client.py b/langfuse/api/legacy/score_v1/raw_client.py index 834560ec9..3dc0164e0 100644 --- a/langfuse/api/legacy/score_v1/raw_client.py +++ b/langfuse/api/legacy/score_v1/raw_client.py @@ -18,6 +18,7 @@ from ...core.request_options import RequestOptions from ...core.serialization import convert_and_respect_annotation_metadata from .types.create_score_response import CreateScoreResponse +from .types.create_score_source import CreateScoreSource # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -43,6 +44,7 @@ def create( queue_id: typing.Optional[str] = OMIT, data_type: typing.Optional[ScoreDataType] = OMIT, config_id: typing.Optional[str] = OMIT, + source: typing.Optional[CreateScoreSource] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[CreateScoreResponse]: """ @@ -81,6 +83,9 @@ def create( config_id : typing.Optional[str] Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + source : typing.Optional[CreateScoreSource] + The source of the score. Defaults to API. Set to ANNOTATION to prefill scores (e.g. from an LLM) for a human reviewer to verify in an annotation queue. When source is ANNOTATION, a configId is required unless dataType is CORRECTION. EVAL is reserved for internal evaluator outputs and is not accepted on this endpoint. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -107,6 +112,7 @@ def create( "queueId": queue_id, "dataType": data_type, "configId": config_id, + "source": source, }, request_options=request_options, omit=OMIT, @@ -304,6 +310,7 @@ async def create( queue_id: typing.Optional[str] = OMIT, data_type: typing.Optional[ScoreDataType] = OMIT, config_id: typing.Optional[str] = OMIT, + source: typing.Optional[CreateScoreSource] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[CreateScoreResponse]: """ @@ -342,6 +349,9 @@ async def create( config_id : typing.Optional[str] Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. + source : typing.Optional[CreateScoreSource] + The source of the score. Defaults to API. Set to ANNOTATION to prefill scores (e.g. from an LLM) for a human reviewer to verify in an annotation queue. When source is ANNOTATION, a configId is required unless dataType is CORRECTION. EVAL is reserved for internal evaluator outputs and is not accepted on this endpoint. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -368,6 +378,7 @@ async def create( "queueId": queue_id, "dataType": data_type, "configId": config_id, + "source": source, }, request_options=request_options, omit=OMIT, diff --git a/langfuse/api/legacy/score_v1/types/__init__.py b/langfuse/api/legacy/score_v1/types/__init__.py index 4a759a978..dde25cbb4 100644 --- a/langfuse/api/legacy/score_v1/types/__init__.py +++ b/langfuse/api/legacy/score_v1/types/__init__.py @@ -8,9 +8,11 @@ if typing.TYPE_CHECKING: from .create_score_request import CreateScoreRequest from .create_score_response import CreateScoreResponse + from .create_score_source import CreateScoreSource _dynamic_imports: typing.Dict[str, str] = { "CreateScoreRequest": ".create_score_request", "CreateScoreResponse": ".create_score_response", + "CreateScoreSource": ".create_score_source", } @@ -41,4 +43,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["CreateScoreRequest", "CreateScoreResponse"] +__all__ = ["CreateScoreRequest", "CreateScoreResponse", "CreateScoreSource"] diff --git a/langfuse/api/legacy/score_v1/types/create_score_request.py b/langfuse/api/legacy/score_v1/types/create_score_request.py index a0397bdfc..ef498fe6c 100644 --- a/langfuse/api/legacy/score_v1/types/create_score_request.py +++ b/langfuse/api/legacy/score_v1/types/create_score_request.py @@ -8,6 +8,7 @@ from ....commons.types.score_data_type import ScoreDataType from ....core.pydantic_utilities import UniversalBaseModel from ....core.serialization import FieldMetadata +from .create_score_source import CreateScoreSource class CreateScoreRequest(UniversalBaseModel): @@ -70,6 +71,11 @@ class CreateScoreRequest(UniversalBaseModel): Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. """ + source: typing.Optional[CreateScoreSource] = pydantic.Field(default=None) + """ + The source of the score. Defaults to API. Set to ANNOTATION to prefill scores (e.g. from an LLM) for a human reviewer to verify in an annotation queue. When source is ANNOTATION, a configId is required unless dataType is CORRECTION. EVAL is reserved for internal evaluator outputs and is not accepted on this endpoint. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) diff --git a/langfuse/api/legacy/score_v1/types/create_score_source.py b/langfuse/api/legacy/score_v1/types/create_score_source.py new file mode 100644 index 000000000..7364efd61 --- /dev/null +++ b/langfuse/api/legacy/score_v1/types/create_score_source.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class CreateScoreSource(enum.StrEnum): + """ + Source values accepted when creating a score via the public REST API. + EVAL is reserved for internal evaluator outputs and is intentionally not + exposed here — use commons.ScoreSource when reading scores. + """ + + API = "API" + ANNOTATION = "ANNOTATION" + + def visit( + self, + api: typing.Callable[[], T_Result], + annotation: typing.Callable[[], T_Result], + ) -> T_Result: + if self is CreateScoreSource.API: + return api() + if self is CreateScoreSource.ANNOTATION: + return annotation() diff --git a/langfuse/api/llm_connections/__init__.py b/langfuse/api/llm_connections/__init__.py index aba7157f1..e4edb011c 100644 --- a/langfuse/api/llm_connections/__init__.py +++ b/langfuse/api/llm_connections/__init__.py @@ -7,12 +7,14 @@ if typing.TYPE_CHECKING: from .types import ( + DeleteLlmConnectionResponse, LlmAdapter, LlmConnection, PaginatedLlmConnections, UpsertLlmConnectionRequest, ) _dynamic_imports: typing.Dict[str, str] = { + "DeleteLlmConnectionResponse": ".types", "LlmAdapter": ".types", "LlmConnection": ".types", "PaginatedLlmConnections": ".types", @@ -48,6 +50,7 @@ def __dir__(): __all__ = [ + "DeleteLlmConnectionResponse", "LlmAdapter", "LlmConnection", "PaginatedLlmConnections", diff --git a/langfuse/api/llm_connections/client.py b/langfuse/api/llm_connections/client.py index 213e55e9f..62c4293ff 100644 --- a/langfuse/api/llm_connections/client.py +++ b/langfuse/api/llm_connections/client.py @@ -5,6 +5,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions from .raw_client import AsyncRawLlmConnectionsClient, RawLlmConnectionsClient +from .types.delete_llm_connection_response import DeleteLlmConnectionResponse from .types.llm_adapter import LlmAdapter from .types.llm_connection import LlmConnection from .types.paginated_llm_connections import PaginatedLlmConnections @@ -153,6 +154,42 @@ def upsert( ) return _response.data + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteLlmConnectionResponse: + """ + Delete an LLM connection by id. Evaluators that depend on the deleted connection are automatically paused. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteLlmConnectionResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.llm_connections.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + class AsyncLlmConnectionsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -309,3 +346,47 @@ async def main() -> None: request_options=request_options, ) return _response.data + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteLlmConnectionResponse: + """ + Delete an LLM connection by id. Evaluators that depend on the deleted connection are automatically paused. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteLlmConnectionResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.llm_connections.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/langfuse/api/llm_connections/raw_client.py b/langfuse/api/llm_connections/raw_client.py index ef4f87425..30f7beebb 100644 --- a/langfuse/api/llm_connections/raw_client.py +++ b/langfuse/api/llm_connections/raw_client.py @@ -11,8 +11,10 @@ from ..core.api_error import ApiError from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import jsonable_encoder from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions +from .types.delete_llm_connection_response import DeleteLlmConnectionResponse from .types.llm_adapter import LlmAdapter from .types.llm_connection import LlmConnection from .types.paginated_llm_connections import PaginatedLlmConnections @@ -280,6 +282,106 @@ def upsert( body=_response_json, ) + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteLlmConnectionResponse]: + """ + Delete an LLM connection by id. Evaluators that depend on the deleted connection are automatically paused. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteLlmConnectionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/llm-connections/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteLlmConnectionResponse, + parse_obj_as( + type_=DeleteLlmConnectionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + class AsyncRawLlmConnectionsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -539,3 +641,103 @@ async def upsert( headers=dict(_response.headers), body=_response_json, ) + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteLlmConnectionResponse]: + """ + Delete an LLM connection by id. Evaluators that depend on the deleted connection are automatically paused. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteLlmConnectionResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/llm-connections/{jsonable_encoder(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteLlmConnectionResponse, + parse_obj_as( + type_=DeleteLlmConnectionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/llm_connections/types/__init__.py b/langfuse/api/llm_connections/types/__init__.py index e6ba89200..ab24fc400 100644 --- a/langfuse/api/llm_connections/types/__init__.py +++ b/langfuse/api/llm_connections/types/__init__.py @@ -6,11 +6,13 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .delete_llm_connection_response import DeleteLlmConnectionResponse from .llm_adapter import LlmAdapter from .llm_connection import LlmConnection from .paginated_llm_connections import PaginatedLlmConnections from .upsert_llm_connection_request import UpsertLlmConnectionRequest _dynamic_imports: typing.Dict[str, str] = { + "DeleteLlmConnectionResponse": ".delete_llm_connection_response", "LlmAdapter": ".llm_adapter", "LlmConnection": ".llm_connection", "PaginatedLlmConnections": ".paginated_llm_connections", @@ -46,6 +48,7 @@ def __dir__(): __all__ = [ + "DeleteLlmConnectionResponse", "LlmAdapter", "LlmConnection", "PaginatedLlmConnections", diff --git a/langfuse/api/llm_connections/types/delete_llm_connection_response.py b/langfuse/api/llm_connections/types/delete_llm_connection_response.py new file mode 100644 index 000000000..080a1904c --- /dev/null +++ b/langfuse/api/llm_connections/types/delete_llm_connection_response.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class DeleteLlmConnectionResponse(UniversalBaseModel): + message: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/observations/client.py b/langfuse/api/observations/client.py index ce0de0cf2..ff7069ede 100644 --- a/langfuse/api/observations/client.py +++ b/langfuse/api/observations/client.py @@ -62,9 +62,10 @@ def get_many( - `io` - input, output - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost + - `usage` - usageDetails, costDetails, totalCost, usagePricingTierName - `prompt` - promptId, promptName, promptVersion - `metrics` - latency, timeToFirstToken + - `trace_context` - tags, release, traceName If not specified, `core` and `basic` field groups are returned. @@ -76,7 +77,7 @@ def get_many( ---------- fields : typing.Optional[str] Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics, trace_context. If not specified, `core` and `basic` field groups are returned. Example: "basic,usage,model" @@ -135,12 +136,12 @@ def get_many( "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - stringOptions: "any of", "none of" // - categoryOptions: "any of", "none of" // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" @@ -192,8 +193,12 @@ def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data + - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + ## Filter Examples ```json [ @@ -215,6 +220,12 @@ def get_many( "key": "environment", "operator": "=", "value": "production" + }, + { + "type": "string", + "column": "output", + "operator": "matches", + "value": "needle" } ] ``` @@ -314,9 +325,10 @@ async def get_many( - `io` - input, output - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost + - `usage` - usageDetails, costDetails, totalCost, usagePricingTierName - `prompt` - promptId, promptName, promptVersion - `metrics` - latency, timeToFirstToken + - `trace_context` - tags, release, traceName If not specified, `core` and `basic` field groups are returned. @@ -328,7 +340,7 @@ async def get_many( ---------- fields : typing.Optional[str] Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics, trace_context. If not specified, `core` and `basic` field groups are returned. Example: "basic,usage,model" @@ -387,12 +399,12 @@ async def get_many( "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - stringOptions: "any of", "none of" // - categoryOptions: "any of", "none of" // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" @@ -444,8 +456,12 @@ async def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data + - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + ## Filter Examples ```json [ @@ -467,6 +483,12 @@ async def get_many( "key": "environment", "operator": "=", "value": "production" + }, + { + "type": "string", + "column": "output", + "operator": "matches", + "value": "needle" } ] ``` diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py index 3ae8eab15..f6502014e 100644 --- a/langfuse/api/observations/raw_client.py +++ b/langfuse/api/observations/raw_client.py @@ -60,9 +60,10 @@ def get_many( - `io` - input, output - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost + - `usage` - usageDetails, costDetails, totalCost, usagePricingTierName - `prompt` - promptId, promptName, promptVersion - `metrics` - latency, timeToFirstToken + - `trace_context` - tags, release, traceName If not specified, `core` and `basic` field groups are returned. @@ -74,7 +75,7 @@ def get_many( ---------- fields : typing.Optional[str] Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics, trace_context. If not specified, `core` and `basic` field groups are returned. Example: "basic,usage,model" @@ -133,12 +134,12 @@ def get_many( "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - stringOptions: "any of", "none of" // - categoryOptions: "any of", "none of" // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" @@ -190,8 +191,12 @@ def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data + - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + ## Filter Examples ```json [ @@ -213,6 +218,12 @@ def get_many( "key": "environment", "operator": "=", "value": "production" + }, + { + "type": "string", + "column": "output", + "operator": "matches", + "value": "needle" } ] ``` @@ -371,9 +382,10 @@ async def get_many( - `io` - input, output - `metadata` - metadata (truncated to 200 chars by default, use `expandMetadata` to get full values) - `model` - providedModelName, internalModelId, modelParameters - - `usage` - usageDetails, costDetails, totalCost + - `usage` - usageDetails, costDetails, totalCost, usagePricingTierName - `prompt` - promptId, promptName, promptVersion - `metrics` - latency, timeToFirstToken + - `trace_context` - tags, release, traceName If not specified, `core` and `basic` field groups are returned. @@ -385,7 +397,7 @@ async def get_many( ---------- fields : typing.Optional[str] Comma-separated list of field groups to include in the response. - Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics. + Available groups: core, basic, time, io, metadata, model, usage, prompt, metrics, trace_context. If not specified, `core` and `basic` field groups are returned. Example: "basic,usage,model" @@ -444,12 +456,12 @@ async def get_many( "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - stringOptions: "any of", "none of" // - categoryOptions: "any of", "none of" // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches" // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" @@ -501,8 +513,12 @@ async def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data + - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + ## Filter Examples ```json [ @@ -524,6 +540,12 @@ async def get_many( "key": "environment", "operator": "=", "value": "production" + }, + { + "type": "string", + "column": "output", + "operator": "matches", + "value": "needle" } ] ``` diff --git a/langfuse/api/score_configs/client.py b/langfuse/api/score_configs/client.py index da6626043..b900e3f5d 100644 --- a/langfuse/api/score_configs/client.py +++ b/langfuse/api/score_configs/client.py @@ -46,6 +46,7 @@ def create( Parameters ---------- name : str + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -204,7 +205,7 @@ def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - The name of the score config + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. categories : typing.Optional[typing.Sequence[ConfigCategory]] Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed @@ -286,6 +287,7 @@ async def create( Parameters ---------- name : str + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -468,7 +470,7 @@ async def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - The name of the score config + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. categories : typing.Optional[typing.Sequence[ConfigCategory]] Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed diff --git a/langfuse/api/score_configs/raw_client.py b/langfuse/api/score_configs/raw_client.py index 8021940c6..11de026c6 100644 --- a/langfuse/api/score_configs/raw_client.py +++ b/langfuse/api/score_configs/raw_client.py @@ -45,6 +45,7 @@ def create( Parameters ---------- name : str + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -400,7 +401,7 @@ def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - The name of the score config + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. categories : typing.Optional[typing.Sequence[ConfigCategory]] Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed @@ -539,6 +540,7 @@ async def create( Parameters ---------- name : str + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -894,7 +896,7 @@ async def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - The name of the score config + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. categories : typing.Optional[typing.Sequence[ConfigCategory]] Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed diff --git a/langfuse/api/score_configs/types/create_score_config_request.py b/langfuse/api/score_configs/types/create_score_config_request.py index 1c23fd91e..0edb01407 100644 --- a/langfuse/api/score_configs/types/create_score_config_request.py +++ b/langfuse/api/score_configs/types/create_score_config_request.py @@ -11,7 +11,11 @@ class CreateScoreConfigRequest(UniversalBaseModel): - name: str + name: str = pydantic.Field() + """ + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + """ + data_type: typing_extensions.Annotated[ ScoreConfigDataType, FieldMetadata(alias="dataType") ] diff --git a/langfuse/api/score_configs/types/update_score_config_request.py b/langfuse/api/score_configs/types/update_score_config_request.py index 5237c544f..28c4248e9 100644 --- a/langfuse/api/score_configs/types/update_score_config_request.py +++ b/langfuse/api/score_configs/types/update_score_config_request.py @@ -19,7 +19,7 @@ class UpdateScoreConfigRequest(UniversalBaseModel): name: typing.Optional[str] = pydantic.Field(default=None) """ - The name of the score config + Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. """ categories: typing.Optional[typing.List[ConfigCategory]] = pydantic.Field( diff --git a/langfuse/api/scores/client.py b/langfuse/api/scores/client.py index 91db2c416..566530e21 100644 --- a/langfuse/api/scores/client.py +++ b/langfuse/api/scores/client.py @@ -62,7 +62,7 @@ def get_many( Page number, starts at 1. limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Limit of items per page. Maximum 100. Defaults to 50. Requests with a limit greater than 100 return HTTP 400. If you encounter api issues due to too large page sizes, try to reduce the limit. user_id : typing.Optional[str] Retrieve only scores with this userId associated to the trace. @@ -258,7 +258,7 @@ async def get_many( Page number, starts at 1. limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Limit of items per page. Maximum 100. Defaults to 50. Requests with a limit greater than 100 return HTTP 400. If you encounter api issues due to too large page sizes, try to reduce the limit. user_id : typing.Optional[str] Retrieve only scores with this userId associated to the trace. diff --git a/langfuse/api/scores/raw_client.py b/langfuse/api/scores/raw_client.py index 2dc16e688..d1508545c 100644 --- a/langfuse/api/scores/raw_client.py +++ b/langfuse/api/scores/raw_client.py @@ -61,7 +61,7 @@ def get_many( Page number, starts at 1. limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Limit of items per page. Maximum 100. Defaults to 50. Requests with a limit greater than 100 return HTTP 400. If you encounter api issues due to too large page sizes, try to reduce the limit. user_id : typing.Optional[str] Retrieve only scores with this userId associated to the trace. @@ -378,7 +378,7 @@ async def get_many( Page number, starts at 1. limit : typing.Optional[int] - Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. + Limit of items per page. Maximum 100. Defaults to 50. Requests with a limit greater than 100 return HTTP 400. If you encounter api issues due to too large page sizes, try to reduce the limit. user_id : typing.Optional[str] Retrieve only scores with this userId associated to the trace. diff --git a/langfuse/api/unstable/__init__.py b/langfuse/api/unstable/__init__.py new file mode 100644 index 000000000..75aafdc24 --- /dev/null +++ b/langfuse/api/unstable/__init__.py @@ -0,0 +1,267 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .errors import ( + AccessDeniedError, + BadRequestError, + ConflictError, + InternalServerError, + MethodNotAllowedError, + NotFoundError, + PublicApiError, + PublicApiErrorCode, + PublicApiErrorDetails, + PublicApiValidationIssue, + TooManyRequestsError, + UnauthorizedError, + UnprocessableContentError, + ) + from . import commons, errors, evaluation_rules, evaluators + from .commons import ( + ArrayOptionsEvaluationRuleFilter, + BooleanEvaluationRuleFilter, + CategoryOptionsEvaluationRuleFilter, + DateTimeEvaluationRuleFilter, + EvaluationRuleArrayOptionsFilterOperator, + EvaluationRuleBooleanFilterOperator, + EvaluationRuleFilter, + EvaluationRuleFilter_ArrayOptions, + EvaluationRuleFilter_Boolean, + EvaluationRuleFilter_CategoryOptions, + EvaluationRuleFilter_Datetime, + EvaluationRuleFilter_Null, + EvaluationRuleFilter_Number, + EvaluationRuleFilter_NumberObject, + EvaluationRuleFilter_String, + EvaluationRuleFilter_StringObject, + EvaluationRuleFilter_StringOptions, + EvaluationRuleMapping, + EvaluationRuleMappingSource, + EvaluationRuleNullFilterOperator, + EvaluationRuleNumberFilterOperator, + EvaluationRuleOptionsFilterOperator, + EvaluationRuleStatus, + EvaluationRuleStringFilterOperator, + EvaluationRuleTarget, + EvaluatorModelConfig, + EvaluatorOutputDataType, + EvaluatorOutputDefinition, + EvaluatorOutputDefinition_Boolean, + EvaluatorOutputDefinition_Categorical, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + EvaluatorScope, + EvaluatorType, + NullEvaluationRuleFilter, + NumberEvaluationRuleFilter, + NumberObjectEvaluationRuleFilter, + PublicBooleanEvaluatorOutputDefinition, + PublicCategoricalEvaluatorOutputDefinition, + PublicCategoricalEvaluatorOutputScoreDefinition, + PublicEvaluatorOutputDefinition, + PublicEvaluatorOutputDefinition_Boolean, + PublicEvaluatorOutputDefinition_Categorical, + PublicEvaluatorOutputDefinition_Numeric, + PublicNumericEvaluatorOutputDefinition, + StringEvaluationRuleFilter, + StringObjectEvaluationRuleFilter, + StringOptionsEvaluationRuleFilter, + ) + from .evaluation_rules import ( + CreateEvaluationRuleRequest, + DeleteEvaluationRuleResponse, + EvaluationRule, + EvaluationRuleEvaluator, + EvaluationRuleEvaluatorReference, + EvaluationRules, + UpdateEvaluationRuleRequest, + ) + from .evaluators import CreateEvaluatorRequest, Evaluator, Evaluators +_dynamic_imports: typing.Dict[str, str] = { + "AccessDeniedError": ".errors", + "ArrayOptionsEvaluationRuleFilter": ".commons", + "BadRequestError": ".errors", + "BooleanEvaluationRuleFilter": ".commons", + "CategoryOptionsEvaluationRuleFilter": ".commons", + "ConflictError": ".errors", + "CreateEvaluationRuleRequest": ".evaluation_rules", + "CreateEvaluatorRequest": ".evaluators", + "DateTimeEvaluationRuleFilter": ".commons", + "DeleteEvaluationRuleResponse": ".evaluation_rules", + "EvaluationRule": ".evaluation_rules", + "EvaluationRuleArrayOptionsFilterOperator": ".commons", + "EvaluationRuleBooleanFilterOperator": ".commons", + "EvaluationRuleEvaluator": ".evaluation_rules", + "EvaluationRuleEvaluatorReference": ".evaluation_rules", + "EvaluationRuleFilter": ".commons", + "EvaluationRuleFilter_ArrayOptions": ".commons", + "EvaluationRuleFilter_Boolean": ".commons", + "EvaluationRuleFilter_CategoryOptions": ".commons", + "EvaluationRuleFilter_Datetime": ".commons", + "EvaluationRuleFilter_Null": ".commons", + "EvaluationRuleFilter_Number": ".commons", + "EvaluationRuleFilter_NumberObject": ".commons", + "EvaluationRuleFilter_String": ".commons", + "EvaluationRuleFilter_StringObject": ".commons", + "EvaluationRuleFilter_StringOptions": ".commons", + "EvaluationRuleMapping": ".commons", + "EvaluationRuleMappingSource": ".commons", + "EvaluationRuleNullFilterOperator": ".commons", + "EvaluationRuleNumberFilterOperator": ".commons", + "EvaluationRuleOptionsFilterOperator": ".commons", + "EvaluationRuleStatus": ".commons", + "EvaluationRuleStringFilterOperator": ".commons", + "EvaluationRuleTarget": ".commons", + "EvaluationRules": ".evaluation_rules", + "Evaluator": ".evaluators", + "EvaluatorModelConfig": ".commons", + "EvaluatorOutputDataType": ".commons", + "EvaluatorOutputDefinition": ".commons", + "EvaluatorOutputDefinition_Boolean": ".commons", + "EvaluatorOutputDefinition_Categorical": ".commons", + "EvaluatorOutputDefinition_Numeric": ".commons", + "EvaluatorOutputFieldDefinition": ".commons", + "EvaluatorScope": ".commons", + "EvaluatorType": ".commons", + "Evaluators": ".evaluators", + "InternalServerError": ".errors", + "MethodNotAllowedError": ".errors", + "NotFoundError": ".errors", + "NullEvaluationRuleFilter": ".commons", + "NumberEvaluationRuleFilter": ".commons", + "NumberObjectEvaluationRuleFilter": ".commons", + "PublicApiError": ".errors", + "PublicApiErrorCode": ".errors", + "PublicApiErrorDetails": ".errors", + "PublicApiValidationIssue": ".errors", + "PublicBooleanEvaluatorOutputDefinition": ".commons", + "PublicCategoricalEvaluatorOutputDefinition": ".commons", + "PublicCategoricalEvaluatorOutputScoreDefinition": ".commons", + "PublicEvaluatorOutputDefinition": ".commons", + "PublicEvaluatorOutputDefinition_Boolean": ".commons", + "PublicEvaluatorOutputDefinition_Categorical": ".commons", + "PublicEvaluatorOutputDefinition_Numeric": ".commons", + "PublicNumericEvaluatorOutputDefinition": ".commons", + "StringEvaluationRuleFilter": ".commons", + "StringObjectEvaluationRuleFilter": ".commons", + "StringOptionsEvaluationRuleFilter": ".commons", + "TooManyRequestsError": ".errors", + "UnauthorizedError": ".errors", + "UnprocessableContentError": ".errors", + "UpdateEvaluationRuleRequest": ".evaluation_rules", + "commons": ".commons", + "errors": ".errors", + "evaluation_rules": ".evaluation_rules", + "evaluators": ".evaluators", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccessDeniedError", + "ArrayOptionsEvaluationRuleFilter", + "BadRequestError", + "BooleanEvaluationRuleFilter", + "CategoryOptionsEvaluationRuleFilter", + "ConflictError", + "CreateEvaluationRuleRequest", + "CreateEvaluatorRequest", + "DateTimeEvaluationRuleFilter", + "DeleteEvaluationRuleResponse", + "EvaluationRule", + "EvaluationRuleArrayOptionsFilterOperator", + "EvaluationRuleBooleanFilterOperator", + "EvaluationRuleEvaluator", + "EvaluationRuleEvaluatorReference", + "EvaluationRuleFilter", + "EvaluationRuleFilter_ArrayOptions", + "EvaluationRuleFilter_Boolean", + "EvaluationRuleFilter_CategoryOptions", + "EvaluationRuleFilter_Datetime", + "EvaluationRuleFilter_Null", + "EvaluationRuleFilter_Number", + "EvaluationRuleFilter_NumberObject", + "EvaluationRuleFilter_String", + "EvaluationRuleFilter_StringObject", + "EvaluationRuleFilter_StringOptions", + "EvaluationRuleMapping", + "EvaluationRuleMappingSource", + "EvaluationRuleNullFilterOperator", + "EvaluationRuleNumberFilterOperator", + "EvaluationRuleOptionsFilterOperator", + "EvaluationRuleStatus", + "EvaluationRuleStringFilterOperator", + "EvaluationRuleTarget", + "EvaluationRules", + "Evaluator", + "EvaluatorModelConfig", + "EvaluatorOutputDataType", + "EvaluatorOutputDefinition", + "EvaluatorOutputDefinition_Boolean", + "EvaluatorOutputDefinition_Categorical", + "EvaluatorOutputDefinition_Numeric", + "EvaluatorOutputFieldDefinition", + "EvaluatorScope", + "EvaluatorType", + "Evaluators", + "InternalServerError", + "MethodNotAllowedError", + "NotFoundError", + "NullEvaluationRuleFilter", + "NumberEvaluationRuleFilter", + "NumberObjectEvaluationRuleFilter", + "PublicApiError", + "PublicApiErrorCode", + "PublicApiErrorDetails", + "PublicApiValidationIssue", + "PublicBooleanEvaluatorOutputDefinition", + "PublicCategoricalEvaluatorOutputDefinition", + "PublicCategoricalEvaluatorOutputScoreDefinition", + "PublicEvaluatorOutputDefinition", + "PublicEvaluatorOutputDefinition_Boolean", + "PublicEvaluatorOutputDefinition_Categorical", + "PublicEvaluatorOutputDefinition_Numeric", + "PublicNumericEvaluatorOutputDefinition", + "StringEvaluationRuleFilter", + "StringObjectEvaluationRuleFilter", + "StringOptionsEvaluationRuleFilter", + "TooManyRequestsError", + "UnauthorizedError", + "UnprocessableContentError", + "UpdateEvaluationRuleRequest", + "commons", + "errors", + "evaluation_rules", + "evaluators", +] diff --git a/langfuse/api/unstable/client.py b/langfuse/api/unstable/client.py new file mode 100644 index 000000000..5c3ac32d7 --- /dev/null +++ b/langfuse/api/unstable/client.py @@ -0,0 +1,91 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawUnstableClient, RawUnstableClient + +if typing.TYPE_CHECKING: + from .evaluation_rules.client import ( + AsyncEvaluationRulesClient, + EvaluationRulesClient, + ) + from .evaluators.client import AsyncEvaluatorsClient, EvaluatorsClient + + +class UnstableClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawUnstableClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._evaluation_rules: typing.Optional[EvaluationRulesClient] = None + self._evaluators: typing.Optional[EvaluatorsClient] = None + + @property + def with_raw_response(self) -> RawUnstableClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawUnstableClient + """ + return self._raw_client + + @property + def evaluation_rules(self): + if self._evaluation_rules is None: + from .evaluation_rules.client import EvaluationRulesClient # noqa: E402 + + self._evaluation_rules = EvaluationRulesClient( + client_wrapper=self._client_wrapper + ) + return self._evaluation_rules + + @property + def evaluators(self): + if self._evaluators is None: + from .evaluators.client import EvaluatorsClient # noqa: E402 + + self._evaluators = EvaluatorsClient(client_wrapper=self._client_wrapper) + return self._evaluators + + +class AsyncUnstableClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawUnstableClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._evaluation_rules: typing.Optional[AsyncEvaluationRulesClient] = None + self._evaluators: typing.Optional[AsyncEvaluatorsClient] = None + + @property + def with_raw_response(self) -> AsyncRawUnstableClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawUnstableClient + """ + return self._raw_client + + @property + def evaluation_rules(self): + if self._evaluation_rules is None: + from .evaluation_rules.client import AsyncEvaluationRulesClient # noqa: E402 + + self._evaluation_rules = AsyncEvaluationRulesClient( + client_wrapper=self._client_wrapper + ) + return self._evaluation_rules + + @property + def evaluators(self): + if self._evaluators is None: + from .evaluators.client import AsyncEvaluatorsClient # noqa: E402 + + self._evaluators = AsyncEvaluatorsClient( + client_wrapper=self._client_wrapper + ) + return self._evaluators diff --git a/langfuse/api/unstable/commons/__init__.py b/langfuse/api/unstable/commons/__init__.py new file mode 100644 index 000000000..13d9571ff --- /dev/null +++ b/langfuse/api/unstable/commons/__init__.py @@ -0,0 +1,187 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + ArrayOptionsEvaluationRuleFilter, + BooleanEvaluationRuleFilter, + CategoryOptionsEvaluationRuleFilter, + DateTimeEvaluationRuleFilter, + EvaluationRuleArrayOptionsFilterOperator, + EvaluationRuleBooleanFilterOperator, + EvaluationRuleFilter, + EvaluationRuleFilter_ArrayOptions, + EvaluationRuleFilter_Boolean, + EvaluationRuleFilter_CategoryOptions, + EvaluationRuleFilter_Datetime, + EvaluationRuleFilter_Null, + EvaluationRuleFilter_Number, + EvaluationRuleFilter_NumberObject, + EvaluationRuleFilter_String, + EvaluationRuleFilter_StringObject, + EvaluationRuleFilter_StringOptions, + EvaluationRuleMapping, + EvaluationRuleMappingSource, + EvaluationRuleNullFilterOperator, + EvaluationRuleNumberFilterOperator, + EvaluationRuleOptionsFilterOperator, + EvaluationRuleStatus, + EvaluationRuleStringFilterOperator, + EvaluationRuleTarget, + EvaluatorModelConfig, + EvaluatorOutputDataType, + EvaluatorOutputDefinition, + EvaluatorOutputDefinition_Boolean, + EvaluatorOutputDefinition_Categorical, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + EvaluatorScope, + EvaluatorType, + NullEvaluationRuleFilter, + NumberEvaluationRuleFilter, + NumberObjectEvaluationRuleFilter, + PublicBooleanEvaluatorOutputDefinition, + PublicCategoricalEvaluatorOutputDefinition, + PublicCategoricalEvaluatorOutputScoreDefinition, + PublicEvaluatorOutputDefinition, + PublicEvaluatorOutputDefinition_Boolean, + PublicEvaluatorOutputDefinition_Categorical, + PublicEvaluatorOutputDefinition_Numeric, + PublicNumericEvaluatorOutputDefinition, + StringEvaluationRuleFilter, + StringObjectEvaluationRuleFilter, + StringOptionsEvaluationRuleFilter, + ) +_dynamic_imports: typing.Dict[str, str] = { + "ArrayOptionsEvaluationRuleFilter": ".types", + "BooleanEvaluationRuleFilter": ".types", + "CategoryOptionsEvaluationRuleFilter": ".types", + "DateTimeEvaluationRuleFilter": ".types", + "EvaluationRuleArrayOptionsFilterOperator": ".types", + "EvaluationRuleBooleanFilterOperator": ".types", + "EvaluationRuleFilter": ".types", + "EvaluationRuleFilter_ArrayOptions": ".types", + "EvaluationRuleFilter_Boolean": ".types", + "EvaluationRuleFilter_CategoryOptions": ".types", + "EvaluationRuleFilter_Datetime": ".types", + "EvaluationRuleFilter_Null": ".types", + "EvaluationRuleFilter_Number": ".types", + "EvaluationRuleFilter_NumberObject": ".types", + "EvaluationRuleFilter_String": ".types", + "EvaluationRuleFilter_StringObject": ".types", + "EvaluationRuleFilter_StringOptions": ".types", + "EvaluationRuleMapping": ".types", + "EvaluationRuleMappingSource": ".types", + "EvaluationRuleNullFilterOperator": ".types", + "EvaluationRuleNumberFilterOperator": ".types", + "EvaluationRuleOptionsFilterOperator": ".types", + "EvaluationRuleStatus": ".types", + "EvaluationRuleStringFilterOperator": ".types", + "EvaluationRuleTarget": ".types", + "EvaluatorModelConfig": ".types", + "EvaluatorOutputDataType": ".types", + "EvaluatorOutputDefinition": ".types", + "EvaluatorOutputDefinition_Boolean": ".types", + "EvaluatorOutputDefinition_Categorical": ".types", + "EvaluatorOutputDefinition_Numeric": ".types", + "EvaluatorOutputFieldDefinition": ".types", + "EvaluatorScope": ".types", + "EvaluatorType": ".types", + "NullEvaluationRuleFilter": ".types", + "NumberEvaluationRuleFilter": ".types", + "NumberObjectEvaluationRuleFilter": ".types", + "PublicBooleanEvaluatorOutputDefinition": ".types", + "PublicCategoricalEvaluatorOutputDefinition": ".types", + "PublicCategoricalEvaluatorOutputScoreDefinition": ".types", + "PublicEvaluatorOutputDefinition": ".types", + "PublicEvaluatorOutputDefinition_Boolean": ".types", + "PublicEvaluatorOutputDefinition_Categorical": ".types", + "PublicEvaluatorOutputDefinition_Numeric": ".types", + "PublicNumericEvaluatorOutputDefinition": ".types", + "StringEvaluationRuleFilter": ".types", + "StringObjectEvaluationRuleFilter": ".types", + "StringOptionsEvaluationRuleFilter": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ArrayOptionsEvaluationRuleFilter", + "BooleanEvaluationRuleFilter", + "CategoryOptionsEvaluationRuleFilter", + "DateTimeEvaluationRuleFilter", + "EvaluationRuleArrayOptionsFilterOperator", + "EvaluationRuleBooleanFilterOperator", + "EvaluationRuleFilter", + "EvaluationRuleFilter_ArrayOptions", + "EvaluationRuleFilter_Boolean", + "EvaluationRuleFilter_CategoryOptions", + "EvaluationRuleFilter_Datetime", + "EvaluationRuleFilter_Null", + "EvaluationRuleFilter_Number", + "EvaluationRuleFilter_NumberObject", + "EvaluationRuleFilter_String", + "EvaluationRuleFilter_StringObject", + "EvaluationRuleFilter_StringOptions", + "EvaluationRuleMapping", + "EvaluationRuleMappingSource", + "EvaluationRuleNullFilterOperator", + "EvaluationRuleNumberFilterOperator", + "EvaluationRuleOptionsFilterOperator", + "EvaluationRuleStatus", + "EvaluationRuleStringFilterOperator", + "EvaluationRuleTarget", + "EvaluatorModelConfig", + "EvaluatorOutputDataType", + "EvaluatorOutputDefinition", + "EvaluatorOutputDefinition_Boolean", + "EvaluatorOutputDefinition_Categorical", + "EvaluatorOutputDefinition_Numeric", + "EvaluatorOutputFieldDefinition", + "EvaluatorScope", + "EvaluatorType", + "NullEvaluationRuleFilter", + "NumberEvaluationRuleFilter", + "NumberObjectEvaluationRuleFilter", + "PublicBooleanEvaluatorOutputDefinition", + "PublicCategoricalEvaluatorOutputDefinition", + "PublicCategoricalEvaluatorOutputScoreDefinition", + "PublicEvaluatorOutputDefinition", + "PublicEvaluatorOutputDefinition_Boolean", + "PublicEvaluatorOutputDefinition_Categorical", + "PublicEvaluatorOutputDefinition_Numeric", + "PublicNumericEvaluatorOutputDefinition", + "StringEvaluationRuleFilter", + "StringObjectEvaluationRuleFilter", + "StringOptionsEvaluationRuleFilter", +] diff --git a/langfuse/api/unstable/commons/types/__init__.py b/langfuse/api/unstable/commons/types/__init__.py new file mode 100644 index 000000000..a0e7d9f9d --- /dev/null +++ b/langfuse/api/unstable/commons/types/__init__.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .array_options_evaluation_rule_filter import ArrayOptionsEvaluationRuleFilter + from .boolean_evaluation_rule_filter import BooleanEvaluationRuleFilter + from .category_options_evaluation_rule_filter import ( + CategoryOptionsEvaluationRuleFilter, + ) + from .date_time_evaluation_rule_filter import DateTimeEvaluationRuleFilter + from .evaluation_rule_array_options_filter_operator import ( + EvaluationRuleArrayOptionsFilterOperator, + ) + from .evaluation_rule_boolean_filter_operator import ( + EvaluationRuleBooleanFilterOperator, + ) + from .evaluation_rule_filter import ( + EvaluationRuleFilter, + EvaluationRuleFilter_ArrayOptions, + EvaluationRuleFilter_Boolean, + EvaluationRuleFilter_CategoryOptions, + EvaluationRuleFilter_Datetime, + EvaluationRuleFilter_Null, + EvaluationRuleFilter_Number, + EvaluationRuleFilter_NumberObject, + EvaluationRuleFilter_String, + EvaluationRuleFilter_StringObject, + EvaluationRuleFilter_StringOptions, + ) + from .evaluation_rule_mapping import EvaluationRuleMapping + from .evaluation_rule_mapping_source import EvaluationRuleMappingSource + from .evaluation_rule_null_filter_operator import EvaluationRuleNullFilterOperator + from .evaluation_rule_number_filter_operator import ( + EvaluationRuleNumberFilterOperator, + ) + from .evaluation_rule_options_filter_operator import ( + EvaluationRuleOptionsFilterOperator, + ) + from .evaluation_rule_status import EvaluationRuleStatus + from .evaluation_rule_string_filter_operator import ( + EvaluationRuleStringFilterOperator, + ) + from .evaluation_rule_target import EvaluationRuleTarget + from .evaluator_model_config import EvaluatorModelConfig + from .evaluator_output_data_type import EvaluatorOutputDataType + from .evaluator_output_definition import ( + EvaluatorOutputDefinition, + EvaluatorOutputDefinition_Boolean, + EvaluatorOutputDefinition_Categorical, + EvaluatorOutputDefinition_Numeric, + ) + from .evaluator_output_field_definition import EvaluatorOutputFieldDefinition + from .evaluator_scope import EvaluatorScope + from .evaluator_type import EvaluatorType + from .null_evaluation_rule_filter import NullEvaluationRuleFilter + from .number_evaluation_rule_filter import NumberEvaluationRuleFilter + from .number_object_evaluation_rule_filter import NumberObjectEvaluationRuleFilter + from .public_boolean_evaluator_output_definition import ( + PublicBooleanEvaluatorOutputDefinition, + ) + from .public_categorical_evaluator_output_definition import ( + PublicCategoricalEvaluatorOutputDefinition, + ) + from .public_categorical_evaluator_output_score_definition import ( + PublicCategoricalEvaluatorOutputScoreDefinition, + ) + from .public_evaluator_output_definition import ( + PublicEvaluatorOutputDefinition, + PublicEvaluatorOutputDefinition_Boolean, + PublicEvaluatorOutputDefinition_Categorical, + PublicEvaluatorOutputDefinition_Numeric, + ) + from .public_numeric_evaluator_output_definition import ( + PublicNumericEvaluatorOutputDefinition, + ) + from .string_evaluation_rule_filter import StringEvaluationRuleFilter + from .string_object_evaluation_rule_filter import StringObjectEvaluationRuleFilter + from .string_options_evaluation_rule_filter import StringOptionsEvaluationRuleFilter +_dynamic_imports: typing.Dict[str, str] = { + "ArrayOptionsEvaluationRuleFilter": ".array_options_evaluation_rule_filter", + "BooleanEvaluationRuleFilter": ".boolean_evaluation_rule_filter", + "CategoryOptionsEvaluationRuleFilter": ".category_options_evaluation_rule_filter", + "DateTimeEvaluationRuleFilter": ".date_time_evaluation_rule_filter", + "EvaluationRuleArrayOptionsFilterOperator": ".evaluation_rule_array_options_filter_operator", + "EvaluationRuleBooleanFilterOperator": ".evaluation_rule_boolean_filter_operator", + "EvaluationRuleFilter": ".evaluation_rule_filter", + "EvaluationRuleFilter_ArrayOptions": ".evaluation_rule_filter", + "EvaluationRuleFilter_Boolean": ".evaluation_rule_filter", + "EvaluationRuleFilter_CategoryOptions": ".evaluation_rule_filter", + "EvaluationRuleFilter_Datetime": ".evaluation_rule_filter", + "EvaluationRuleFilter_Null": ".evaluation_rule_filter", + "EvaluationRuleFilter_Number": ".evaluation_rule_filter", + "EvaluationRuleFilter_NumberObject": ".evaluation_rule_filter", + "EvaluationRuleFilter_String": ".evaluation_rule_filter", + "EvaluationRuleFilter_StringObject": ".evaluation_rule_filter", + "EvaluationRuleFilter_StringOptions": ".evaluation_rule_filter", + "EvaluationRuleMapping": ".evaluation_rule_mapping", + "EvaluationRuleMappingSource": ".evaluation_rule_mapping_source", + "EvaluationRuleNullFilterOperator": ".evaluation_rule_null_filter_operator", + "EvaluationRuleNumberFilterOperator": ".evaluation_rule_number_filter_operator", + "EvaluationRuleOptionsFilterOperator": ".evaluation_rule_options_filter_operator", + "EvaluationRuleStatus": ".evaluation_rule_status", + "EvaluationRuleStringFilterOperator": ".evaluation_rule_string_filter_operator", + "EvaluationRuleTarget": ".evaluation_rule_target", + "EvaluatorModelConfig": ".evaluator_model_config", + "EvaluatorOutputDataType": ".evaluator_output_data_type", + "EvaluatorOutputDefinition": ".evaluator_output_definition", + "EvaluatorOutputDefinition_Boolean": ".evaluator_output_definition", + "EvaluatorOutputDefinition_Categorical": ".evaluator_output_definition", + "EvaluatorOutputDefinition_Numeric": ".evaluator_output_definition", + "EvaluatorOutputFieldDefinition": ".evaluator_output_field_definition", + "EvaluatorScope": ".evaluator_scope", + "EvaluatorType": ".evaluator_type", + "NullEvaluationRuleFilter": ".null_evaluation_rule_filter", + "NumberEvaluationRuleFilter": ".number_evaluation_rule_filter", + "NumberObjectEvaluationRuleFilter": ".number_object_evaluation_rule_filter", + "PublicBooleanEvaluatorOutputDefinition": ".public_boolean_evaluator_output_definition", + "PublicCategoricalEvaluatorOutputDefinition": ".public_categorical_evaluator_output_definition", + "PublicCategoricalEvaluatorOutputScoreDefinition": ".public_categorical_evaluator_output_score_definition", + "PublicEvaluatorOutputDefinition": ".public_evaluator_output_definition", + "PublicEvaluatorOutputDefinition_Boolean": ".public_evaluator_output_definition", + "PublicEvaluatorOutputDefinition_Categorical": ".public_evaluator_output_definition", + "PublicEvaluatorOutputDefinition_Numeric": ".public_evaluator_output_definition", + "PublicNumericEvaluatorOutputDefinition": ".public_numeric_evaluator_output_definition", + "StringEvaluationRuleFilter": ".string_evaluation_rule_filter", + "StringObjectEvaluationRuleFilter": ".string_object_evaluation_rule_filter", + "StringOptionsEvaluationRuleFilter": ".string_options_evaluation_rule_filter", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ArrayOptionsEvaluationRuleFilter", + "BooleanEvaluationRuleFilter", + "CategoryOptionsEvaluationRuleFilter", + "DateTimeEvaluationRuleFilter", + "EvaluationRuleArrayOptionsFilterOperator", + "EvaluationRuleBooleanFilterOperator", + "EvaluationRuleFilter", + "EvaluationRuleFilter_ArrayOptions", + "EvaluationRuleFilter_Boolean", + "EvaluationRuleFilter_CategoryOptions", + "EvaluationRuleFilter_Datetime", + "EvaluationRuleFilter_Null", + "EvaluationRuleFilter_Number", + "EvaluationRuleFilter_NumberObject", + "EvaluationRuleFilter_String", + "EvaluationRuleFilter_StringObject", + "EvaluationRuleFilter_StringOptions", + "EvaluationRuleMapping", + "EvaluationRuleMappingSource", + "EvaluationRuleNullFilterOperator", + "EvaluationRuleNumberFilterOperator", + "EvaluationRuleOptionsFilterOperator", + "EvaluationRuleStatus", + "EvaluationRuleStringFilterOperator", + "EvaluationRuleTarget", + "EvaluatorModelConfig", + "EvaluatorOutputDataType", + "EvaluatorOutputDefinition", + "EvaluatorOutputDefinition_Boolean", + "EvaluatorOutputDefinition_Categorical", + "EvaluatorOutputDefinition_Numeric", + "EvaluatorOutputFieldDefinition", + "EvaluatorScope", + "EvaluatorType", + "NullEvaluationRuleFilter", + "NumberEvaluationRuleFilter", + "NumberObjectEvaluationRuleFilter", + "PublicBooleanEvaluatorOutputDefinition", + "PublicCategoricalEvaluatorOutputDefinition", + "PublicCategoricalEvaluatorOutputScoreDefinition", + "PublicEvaluatorOutputDefinition", + "PublicEvaluatorOutputDefinition_Boolean", + "PublicEvaluatorOutputDefinition_Categorical", + "PublicEvaluatorOutputDefinition_Numeric", + "PublicNumericEvaluatorOutputDefinition", + "StringEvaluationRuleFilter", + "StringObjectEvaluationRuleFilter", + "StringOptionsEvaluationRuleFilter", +] diff --git a/langfuse/api/unstable/commons/types/array_options_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/array_options_evaluation_rule_filter.py new file mode 100644 index 000000000..c89ce8b16 --- /dev/null +++ b/langfuse/api/unstable/commons/types/array_options_evaluation_rule_filter.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_array_options_filter_operator import ( + EvaluationRuleArrayOptionsFilterOperator, +) + + +class ArrayOptionsEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. + """ + + operator: EvaluationRuleArrayOptionsFilterOperator + value: typing.List[str] = pydantic.Field() + """ + One or more array elements to match. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/boolean_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/boolean_evaluation_rule_filter.py new file mode 100644 index 000000000..666b691bb --- /dev/null +++ b/langfuse/api/unstable/commons/types/boolean_evaluation_rule_filter.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_boolean_filter_operator import EvaluationRuleBooleanFilterOperator + + +class BooleanEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. + """ + + operator: EvaluationRuleBooleanFilterOperator + value: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/category_options_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/category_options_evaluation_rule_filter.py new file mode 100644 index 000000000..97f13ae62 --- /dev/null +++ b/langfuse/api/unstable/commons/types/category_options_evaluation_rule_filter.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_options_filter_operator import EvaluationRuleOptionsFilterOperator + + +class CategoryOptionsEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Object-valued column to filter on. + """ + + key: str = pydantic.Field() + """ + Key inside the object-valued column to filter on. + """ + + operator: EvaluationRuleOptionsFilterOperator + value: typing.List[str] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/date_time_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/date_time_evaluation_rule_filter.py new file mode 100644 index 000000000..9ee23b1fe --- /dev/null +++ b/langfuse/api/unstable/commons/types/date_time_evaluation_rule_filter.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_number_filter_operator import EvaluationRuleNumberFilterOperator + + +class DateTimeEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. + """ + + operator: EvaluationRuleNumberFilterOperator = pydantic.Field() + """ + Comparison operator for datetime values. + """ + + value: dt.datetime = pydantic.Field() + """ + Datetime value to compare against. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_array_options_filter_operator.py b/langfuse/api/unstable/commons/types/evaluation_rule_array_options_filter_operator.py new file mode 100644 index 000000000..ba8f49a13 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_array_options_filter_operator.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleArrayOptionsFilterOperator(enum.StrEnum): + ANY_OF = "any of" + NONE_OF = "none of" + ALL_OF = "all of" + + def visit( + self, + any_of: typing.Callable[[], T_Result], + none_of: typing.Callable[[], T_Result], + all_of: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleArrayOptionsFilterOperator.ANY_OF: + return any_of() + if self is EvaluationRuleArrayOptionsFilterOperator.NONE_OF: + return none_of() + if self is EvaluationRuleArrayOptionsFilterOperator.ALL_OF: + return all_of() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_boolean_filter_operator.py b/langfuse/api/unstable/commons/types/evaluation_rule_boolean_filter_operator.py new file mode 100644 index 000000000..737d6063a --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_boolean_filter_operator.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleBooleanFilterOperator(enum.StrEnum): + EQUALS = "=" + NOT_EQUALS = "<>" + + def visit( + self, + equals: typing.Callable[[], T_Result], + not_equals: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleBooleanFilterOperator.EQUALS: + return equals() + if self is EvaluationRuleBooleanFilterOperator.NOT_EQUALS: + return not_equals() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/evaluation_rule_filter.py new file mode 100644 index 000000000..ea5e0420b --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_filter.py @@ -0,0 +1,740 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_array_options_filter_operator import ( + EvaluationRuleArrayOptionsFilterOperator, +) +from .evaluation_rule_boolean_filter_operator import EvaluationRuleBooleanFilterOperator +from .evaluation_rule_null_filter_operator import EvaluationRuleNullFilterOperator +from .evaluation_rule_number_filter_operator import EvaluationRuleNumberFilterOperator +from .evaluation_rule_options_filter_operator import EvaluationRuleOptionsFilterOperator +from .evaluation_rule_string_filter_operator import EvaluationRuleStringFilterOperator + + +class EvaluationRuleFilter_Datetime(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["datetime"] = "datetime" + column: str + operator: EvaluationRuleNumberFilterOperator + value: dt.datetime + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_String(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["string"] = "string" + column: str + operator: EvaluationRuleStringFilterOperator + value: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_Number(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["number"] = "number" + column: str + operator: EvaluationRuleNumberFilterOperator + value: float + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_StringOptions(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["stringOptions"] = "stringOptions" + column: str + operator: EvaluationRuleOptionsFilterOperator + value: typing.List[str] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_CategoryOptions(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["categoryOptions"] = "categoryOptions" + column: str + key: str + operator: EvaluationRuleOptionsFilterOperator + value: typing.List[str] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_ArrayOptions(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["arrayOptions"] = "arrayOptions" + column: str + operator: EvaluationRuleArrayOptionsFilterOperator + value: typing.List[str] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_StringObject(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["stringObject"] = "stringObject" + column: str + key: str + operator: EvaluationRuleStringFilterOperator + value: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_NumberObject(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["numberObject"] = "numberObject" + column: str + key: str + operator: EvaluationRuleNumberFilterOperator + value: float + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_Boolean(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["boolean"] = "boolean" + column: str + operator: EvaluationRuleBooleanFilterOperator + value: bool + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluationRuleFilter_Null(UniversalBaseModel): + """ + One filter condition used to decide whether a live-ingested target should be evaluated. + + An evaluation rule can include zero or more filter objects. All filters must be satisfied for the target to run. + + How to build a valid filter object: + - Pick the `target` first, because it changes the supported columns. + - Pick the filter `type`. That determines which fields are required. + - Use `key` only for object filters such as `metadata`. + - Use the correct `value` shape for the chosen filter `type`. + + Operator quick reference by filter `type`: + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends with` + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + - `stringOptions`: `any of`, `none of` + - `arrayOptions`: `any of`, `none of`, `all of` + - `stringObject`: same operators as `string` + - `null`: `is null`, `is not null` + + Supported columns by target: + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + - `invalid_filter_value` with `details.column` but no `invalidValues`: the selected `column` is not supported for the chosen `target` + - `invalid_filter_value` with `details.invalidValues`: the selected values are not allowed for that column. Replace them with one of `details.allowedValues` when provided. + - `invalid_filter_value` for `column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, + ) + + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + """ + + type: typing.Literal["null"] = "null" + column: str + operator: EvaluationRuleNullFilterOperator + value: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +""" +from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleOptionsFilterOperator, +) + +EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], +) +""" +EvaluationRuleFilter = typing_extensions.Annotated[ + typing.Union[ + EvaluationRuleFilter_Datetime, + EvaluationRuleFilter_String, + EvaluationRuleFilter_Number, + EvaluationRuleFilter_StringOptions, + EvaluationRuleFilter_CategoryOptions, + EvaluationRuleFilter_ArrayOptions, + EvaluationRuleFilter_StringObject, + EvaluationRuleFilter_NumberObject, + EvaluationRuleFilter_Boolean, + EvaluationRuleFilter_Null, + ], + pydantic.Field(discriminator="type"), +] diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py b/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py new file mode 100644 index 000000000..1c407819c --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .evaluation_rule_mapping_source import EvaluationRuleMappingSource + + +class EvaluationRuleMapping(UniversalBaseModel): + """ + Maps one evaluator prompt variable to one source field from the target object. + + How to build a valid mapping list: + 1. Create the evaluator or fetch it with `GET /evaluators/{id}`. + 2. Read the evaluator `variables` array. + 3. Add exactly one mapping object for each variable in that array. + 4. Use the variable name exactly as returned, without braces such as `{{` or `}}`. + 5. Choose a `source` that is valid for the selected `target`. + + `jsonPath` is optional. Use it only when the selected source is a JSON object and you want to extract one nested field before inserting it into the evaluator prompt. + + Recovery guidance: + - `invalid_variable_mapping`: the variable name is unknown for this evaluator, or the selected `source` is not valid for the chosen `target` + - `missing_variable_mapping`: one or more evaluator variables are not mapped yet + - `duplicate_variable_mapping`: the same evaluator variable appears more than once + - `invalid_json_path`: the JSONPath expression is malformed. Remove it or correct it. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluationRuleMapping, + EvaluationRuleMappingSource, + ) + + EvaluationRuleMapping( + variable="input", + source=EvaluationRuleMappingSource.INPUT, + ) + """ + + variable: str = pydantic.Field() + """ + Prompt variable name without braces. + + Example: for the prompt `Judge {{input}} against {{output}}`, use `input` and `output`. + """ + + source: EvaluationRuleMappingSource = pydantic.Field() + """ + Source field that should populate the prompt variable. + + Quick reference: + - `target=observation`: `input`, `output`, `metadata` + - `target=experiment`: `input`, `output`, `metadata`, `expected_output`, `experiment_item_metadata` + """ + + json_path: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="jsonPath") + ] = pydantic.Field(default=None) + """ + Optional JSONPath selector applied to the selected source before it is passed to the evaluator prompt. + + Requirements: + - Must start with `$` + - Must be a syntactically valid JSONPath expression + - Most useful with `source=metadata` + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_mapping_source.py b/langfuse/api/unstable/commons/types/evaluation_rule_mapping_source.py new file mode 100644 index 000000000..391c66bbd --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_mapping_source.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleMappingSource(enum.StrEnum): + """ + Source field used to populate a prompt variable. + + Use these values when mapping evaluator prompt variables to live data. + + Target-specific rules: + - `target=observation` supports `input`, `output`, and `metadata` + - `target=experiment` supports `input`, `output`, `metadata`, `expected_output`, and `experiment_item_metadata` + + Source semantics: + - `input`: the observation or experiment input payload + - `output`: the observation or experiment output payload + - `metadata`: the metadata object for the target. Combine with `jsonPath` when you need one nested field instead of the whole object. + - `expected_output`: the experiment item's expected output. Only valid for `target=experiment`. + - `experiment_item_metadata`: the experiment item's metadata object. Only valid for `target=experiment`. + """ + + INPUT = "input" + OUTPUT = "output" + METADATA = "metadata" + EXPECTED_OUTPUT = "expected_output" + EXPERIMENT_ITEM_METADATA = "experiment_item_metadata" + + def visit( + self, + input: typing.Callable[[], T_Result], + output: typing.Callable[[], T_Result], + metadata: typing.Callable[[], T_Result], + expected_output: typing.Callable[[], T_Result], + experiment_item_metadata: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleMappingSource.INPUT: + return input() + if self is EvaluationRuleMappingSource.OUTPUT: + return output() + if self is EvaluationRuleMappingSource.METADATA: + return metadata() + if self is EvaluationRuleMappingSource.EXPECTED_OUTPUT: + return expected_output() + if self is EvaluationRuleMappingSource.EXPERIMENT_ITEM_METADATA: + return experiment_item_metadata() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_null_filter_operator.py b/langfuse/api/unstable/commons/types/evaluation_rule_null_filter_operator.py new file mode 100644 index 000000000..833c8406f --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_null_filter_operator.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleNullFilterOperator(enum.StrEnum): + IS_NULL = "is null" + IS_NOT_NULL = "is not null" + + def visit( + self, + is_null: typing.Callable[[], T_Result], + is_not_null: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleNullFilterOperator.IS_NULL: + return is_null() + if self is EvaluationRuleNullFilterOperator.IS_NOT_NULL: + return is_not_null() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_number_filter_operator.py b/langfuse/api/unstable/commons/types/evaluation_rule_number_filter_operator.py new file mode 100644 index 000000000..927523e04 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_number_filter_operator.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleNumberFilterOperator(enum.StrEnum): + EQUALS = "=" + GREATER_THAN = ">" + LESS_THAN = "<" + GREATER_THAN_OR_EQUAL = ">=" + LESS_THAN_OR_EQUAL = "<=" + + def visit( + self, + equals: typing.Callable[[], T_Result], + greater_than: typing.Callable[[], T_Result], + less_than: typing.Callable[[], T_Result], + greater_than_or_equal: typing.Callable[[], T_Result], + less_than_or_equal: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleNumberFilterOperator.EQUALS: + return equals() + if self is EvaluationRuleNumberFilterOperator.GREATER_THAN: + return greater_than() + if self is EvaluationRuleNumberFilterOperator.LESS_THAN: + return less_than() + if self is EvaluationRuleNumberFilterOperator.GREATER_THAN_OR_EQUAL: + return greater_than_or_equal() + if self is EvaluationRuleNumberFilterOperator.LESS_THAN_OR_EQUAL: + return less_than_or_equal() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_options_filter_operator.py b/langfuse/api/unstable/commons/types/evaluation_rule_options_filter_operator.py new file mode 100644 index 000000000..01cd13ea3 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_options_filter_operator.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleOptionsFilterOperator(enum.StrEnum): + ANY_OF = "any of" + NONE_OF = "none of" + + def visit( + self, + any_of: typing.Callable[[], T_Result], + none_of: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleOptionsFilterOperator.ANY_OF: + return any_of() + if self is EvaluationRuleOptionsFilterOperator.NONE_OF: + return none_of() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_status.py b/langfuse/api/unstable/commons/types/evaluation_rule_status.py new file mode 100644 index 000000000..4a313a962 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_status.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleStatus(enum.StrEnum): + """ + Effective runtime status of the evaluation rule. + + - `active`: enabled and currently runnable. + - `inactive`: disabled by configuration. + - `paused`: enabled, but Langfuse has blocked execution until the underlying issue is resolved. + """ + + ACTIVE = "active" + INACTIVE = "inactive" + PAUSED = "paused" + + def visit( + self, + active: typing.Callable[[], T_Result], + inactive: typing.Callable[[], T_Result], + paused: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleStatus.ACTIVE: + return active() + if self is EvaluationRuleStatus.INACTIVE: + return inactive() + if self is EvaluationRuleStatus.PAUSED: + return paused() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_string_filter_operator.py b/langfuse/api/unstable/commons/types/evaluation_rule_string_filter_operator.py new file mode 100644 index 000000000..9955172b9 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_string_filter_operator.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleStringFilterOperator(enum.StrEnum): + EQUALS = "=" + CONTAINS = "contains" + DOES_NOT_CONTAIN = "does not contain" + STARTS_WITH = "starts with" + ENDS_WITH = "ends with" + + def visit( + self, + equals: typing.Callable[[], T_Result], + contains: typing.Callable[[], T_Result], + does_not_contain: typing.Callable[[], T_Result], + starts_with: typing.Callable[[], T_Result], + ends_with: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleStringFilterOperator.EQUALS: + return equals() + if self is EvaluationRuleStringFilterOperator.CONTAINS: + return contains() + if self is EvaluationRuleStringFilterOperator.DOES_NOT_CONTAIN: + return does_not_contain() + if self is EvaluationRuleStringFilterOperator.STARTS_WITH: + return starts_with() + if self is EvaluationRuleStringFilterOperator.ENDS_WITH: + return ends_with() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_target.py b/langfuse/api/unstable/commons/types/evaluation_rule_target.py new file mode 100644 index 000000000..186aa461c --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluation_rule_target.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluationRuleTarget(enum.StrEnum): + """ + The ingestion object type that should trigger evaluation runs. + + Choose the target first, because it changes both the valid filter columns and the valid variable-mapping sources: + - `observation` evaluates live-ingested observations such as generations, spans, and events. + It supports mapping from `input`, `output`, and `metadata`. + - `experiment` evaluates live experiment executions and can additionally map `expected_output` and `experiment_item_metadata`. + It currently supports filtering by `datasetId`. + Discover valid dataset IDs with `GET /api/public/v2/datasets`, then use the returned dataset `id` values in your filter. + """ + + OBSERVATION = "observation" + EXPERIMENT = "experiment" + + def visit( + self, + observation: typing.Callable[[], T_Result], + experiment: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluationRuleTarget.OBSERVATION: + return observation() + if self is EvaluationRuleTarget.EXPERIMENT: + return experiment() diff --git a/langfuse/api/unstable/commons/types/evaluator_model_config.py b/langfuse/api/unstable/commons/types/evaluator_model_config.py new file mode 100644 index 000000000..5473cca8f --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluator_model_config.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class EvaluatorModelConfig(UniversalBaseModel): + """ + Optional explicit model configuration for an evaluator. + + If omitted, Langfuse uses the project's default evaluation model. + If provided, the model must be available to the project when the evaluator or evaluation rule is enabled. + + To discover valid configured `provider` values for a project, call `GET /api/public/llm-connections` and read the `provider` field from the returned connections. + Use a `provider` value that matches one of the connections already configured in the same project. + + Recovery guidance: + - If evaluator creation returns `422` with `code=evaluator_preflight_failed`, either provide a valid explicit `modelConfig` here or configure the project's default evaluation model, then retry the same request. + + Examples + -------- + from langfuse.unstable.commons import EvaluatorModelConfig + + EvaluatorModelConfig( + provider="openai", + model="gpt-4.1-mini", + ) + """ + + provider: str = pydantic.Field() + """ + Provider identifier to use for this evaluator, for example `openai` or `anthropic`. + + To discover valid values for the current project, call `GET /api/public/llm-connections` and use one of the returned `provider` values. + """ + + model: str = pydantic.Field() + """ + Model identifier exposed by the provider, for example `gpt-4.1-mini`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/evaluator_output_data_type.py b/langfuse/api/unstable/commons/types/evaluator_output_data_type.py new file mode 100644 index 000000000..a6c309868 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluator_output_data_type.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluatorOutputDataType(enum.StrEnum): + """ + Structured score type returned by an evaluator. + + This controls the type of score value Langfuse stores for evaluation results: + - `NUMERIC`: a numeric score such as `0.82` + - `BOOLEAN`: a boolean score such as `true` + - `CATEGORICAL`: one or more category labels from a fixed list + """ + + NUMERIC = "NUMERIC" + BOOLEAN = "BOOLEAN" + CATEGORICAL = "CATEGORICAL" + + def visit( + self, + numeric: typing.Callable[[], T_Result], + boolean: typing.Callable[[], T_Result], + categorical: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluatorOutputDataType.NUMERIC: + return numeric() + if self is EvaluatorOutputDataType.BOOLEAN: + return boolean() + if self is EvaluatorOutputDataType.CATEGORICAL: + return categorical() diff --git a/langfuse/api/unstable/commons/types/evaluator_output_definition.py b/langfuse/api/unstable/commons/types/evaluator_output_definition.py new file mode 100644 index 000000000..f545a19a8 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluator_output_definition.py @@ -0,0 +1,161 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .evaluator_output_field_definition import EvaluatorOutputFieldDefinition +from .public_categorical_evaluator_output_score_definition import ( + PublicCategoricalEvaluatorOutputScoreDefinition, +) + + +class EvaluatorOutputDefinition_Numeric(UniversalBaseModel): + """ + Structured output definition to send when creating an evaluator. + + Agent guidance: + - `dataType` is required. + - Do not send `version`; that is an internal storage detail and is not part of the public request contract. + - For `NUMERIC` and `BOOLEAN`, provide `reasoning.description` and `score.description`. + - For `CATEGORICAL`, also provide `score.categories` and `score.shouldAllowMultipleMatches`. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + ) + + EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), + ) + """ + + data_type: typing_extensions.Annotated[ + typing.Literal["NUMERIC"], FieldMetadata(alias="dataType") + ] = "NUMERIC" + reasoning: EvaluatorOutputFieldDefinition + score: EvaluatorOutputFieldDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluatorOutputDefinition_Boolean(UniversalBaseModel): + """ + Structured output definition to send when creating an evaluator. + + Agent guidance: + - `dataType` is required. + - Do not send `version`; that is an internal storage detail and is not part of the public request contract. + - For `NUMERIC` and `BOOLEAN`, provide `reasoning.description` and `score.description`. + - For `CATEGORICAL`, also provide `score.categories` and `score.shouldAllowMultipleMatches`. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + ) + + EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), + ) + """ + + data_type: typing_extensions.Annotated[ + typing.Literal["BOOLEAN"], FieldMetadata(alias="dataType") + ] = "BOOLEAN" + reasoning: EvaluatorOutputFieldDefinition + score: EvaluatorOutputFieldDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class EvaluatorOutputDefinition_Categorical(UniversalBaseModel): + """ + Structured output definition to send when creating an evaluator. + + Agent guidance: + - `dataType` is required. + - Do not send `version`; that is an internal storage detail and is not part of the public request contract. + - For `NUMERIC` and `BOOLEAN`, provide `reasoning.description` and `score.description`. + - For `CATEGORICAL`, also provide `score.categories` and `score.shouldAllowMultipleMatches`. + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + ) + + EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), + ) + """ + + data_type: typing_extensions.Annotated[ + typing.Literal["CATEGORICAL"], FieldMetadata(alias="dataType") + ] = "CATEGORICAL" + reasoning: EvaluatorOutputFieldDefinition + score: PublicCategoricalEvaluatorOutputScoreDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +""" +from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, +) + +EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), +) +""" +EvaluatorOutputDefinition = typing_extensions.Annotated[ + typing.Union[ + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputDefinition_Boolean, + EvaluatorOutputDefinition_Categorical, + ], + pydantic.Field(discriminator="data_type"), +] diff --git a/langfuse/api/unstable/commons/types/evaluator_output_field_definition.py b/langfuse/api/unstable/commons/types/evaluator_output_field_definition.py new file mode 100644 index 000000000..419610d0a --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluator_output_field_definition.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class EvaluatorOutputFieldDefinition(UniversalBaseModel): + description: str = pydantic.Field() + """ + Human-readable instructions for what the evaluator should return in this field. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/evaluator_scope.py b/langfuse/api/unstable/commons/types/evaluator_scope.py new file mode 100644 index 000000000..7ce796418 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluator_scope.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluatorScope(enum.StrEnum): + """ + Where an evaluator comes from. + + - `project`: created in your project + - `managed`: provided by Langfuse + """ + + PROJECT = "project" + MANAGED = "managed" + + def visit( + self, + project: typing.Callable[[], T_Result], + managed: typing.Callable[[], T_Result], + ) -> T_Result: + if self is EvaluatorScope.PROJECT: + return project() + if self is EvaluatorScope.MANAGED: + return managed() diff --git a/langfuse/api/unstable/commons/types/evaluator_type.py b/langfuse/api/unstable/commons/types/evaluator_type.py new file mode 100644 index 000000000..d411d6111 --- /dev/null +++ b/langfuse/api/unstable/commons/types/evaluator_type.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class EvaluatorType(enum.StrEnum): + """ + The evaluator engine type. + + The unstable public API currently supports only LLM-as-a-judge evaluators. + """ + + LLM_AS_JUDGE = "llm_as_judge" + + def visit(self, llm_as_judge: typing.Callable[[], T_Result]) -> T_Result: + if self is EvaluatorType.LLM_AS_JUDGE: + return llm_as_judge() diff --git a/langfuse/api/unstable/commons/types/null_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/null_evaluation_rule_filter.py new file mode 100644 index 000000000..d224d7590 --- /dev/null +++ b/langfuse/api/unstable/commons/types/null_evaluation_rule_filter.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_null_filter_operator import EvaluationRuleNullFilterOperator + + +class NullEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. In the unstable public API this is currently `parentObservationId`. + """ + + operator: EvaluationRuleNullFilterOperator + value: typing.Optional[str] = pydantic.Field(default=None) + """ + Ignored placeholder value. Clients may omit it or send an empty string. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/number_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/number_evaluation_rule_filter.py new file mode 100644 index 000000000..f9c489291 --- /dev/null +++ b/langfuse/api/unstable/commons/types/number_evaluation_rule_filter.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_number_filter_operator import EvaluationRuleNumberFilterOperator + + +class NumberEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. + """ + + operator: EvaluationRuleNumberFilterOperator + value: float + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/number_object_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/number_object_evaluation_rule_filter.py new file mode 100644 index 000000000..fd9462174 --- /dev/null +++ b/langfuse/api/unstable/commons/types/number_object_evaluation_rule_filter.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_number_filter_operator import EvaluationRuleNumberFilterOperator + + +class NumberObjectEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Object-valued column to filter on. + """ + + key: str = pydantic.Field() + """ + Key inside the object-valued column to filter on. + """ + + operator: EvaluationRuleNumberFilterOperator + value: float + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/public_boolean_evaluator_output_definition.py b/langfuse/api/unstable/commons/types/public_boolean_evaluator_output_definition.py new file mode 100644 index 000000000..7baaf209a --- /dev/null +++ b/langfuse/api/unstable/commons/types/public_boolean_evaluator_output_definition.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .evaluator_output_data_type import EvaluatorOutputDataType +from .evaluator_output_field_definition import EvaluatorOutputFieldDefinition + + +class PublicBooleanEvaluatorOutputDefinition(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + EvaluatorOutputDataType, FieldMetadata(alias="dataType") + ] = pydantic.Field() + """ + Always `BOOLEAN`. + """ + + reasoning: EvaluatorOutputFieldDefinition + score: EvaluatorOutputFieldDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/public_categorical_evaluator_output_definition.py b/langfuse/api/unstable/commons/types/public_categorical_evaluator_output_definition.py new file mode 100644 index 000000000..30d4673bb --- /dev/null +++ b/langfuse/api/unstable/commons/types/public_categorical_evaluator_output_definition.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .evaluator_output_data_type import EvaluatorOutputDataType +from .evaluator_output_field_definition import EvaluatorOutputFieldDefinition +from .public_categorical_evaluator_output_score_definition import ( + PublicCategoricalEvaluatorOutputScoreDefinition, +) + + +class PublicCategoricalEvaluatorOutputDefinition(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + EvaluatorOutputDataType, FieldMetadata(alias="dataType") + ] = pydantic.Field() + """ + Always `CATEGORICAL`. + """ + + reasoning: EvaluatorOutputFieldDefinition + score: PublicCategoricalEvaluatorOutputScoreDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/public_categorical_evaluator_output_score_definition.py b/langfuse/api/unstable/commons/types/public_categorical_evaluator_output_score_definition.py new file mode 100644 index 000000000..81deadb93 --- /dev/null +++ b/langfuse/api/unstable/commons/types/public_categorical_evaluator_output_score_definition.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata + + +class PublicCategoricalEvaluatorOutputScoreDefinition(UniversalBaseModel): + description: str + categories: typing.List[str] + should_allow_multiple_matches: typing_extensions.Annotated[ + bool, FieldMetadata(alias="shouldAllowMultipleMatches") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/public_evaluator_output_definition.py b/langfuse/api/unstable/commons/types/public_evaluator_output_definition.py new file mode 100644 index 000000000..43c7aa9ba --- /dev/null +++ b/langfuse/api/unstable/commons/types/public_evaluator_output_definition.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .evaluator_output_field_definition import EvaluatorOutputFieldDefinition +from .public_categorical_evaluator_output_score_definition import ( + PublicCategoricalEvaluatorOutputScoreDefinition, +) + + +class PublicEvaluatorOutputDefinition_Numeric(UniversalBaseModel): + """ + Evaluator output definition returned by the public API. + + This response always includes `dataType` and never includes an internal output-definition `version`. + Legacy stored evaluator definitions are normalized into this shape before they are returned. + + Use this response shape when deciding how to interpret future evaluation scores: + - `NUMERIC`: expect numeric score values + - `BOOLEAN`: expect `true` / `false` + - `CATEGORICAL`: expect one or more values from `score.categories` + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputFieldDefinition, + PublicEvaluatorOutputDefinition_Numeric, + ) + + PublicEvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), + ) + """ + + data_type: typing_extensions.Annotated[ + typing.Literal["NUMERIC"], FieldMetadata(alias="dataType") + ] = "NUMERIC" + reasoning: EvaluatorOutputFieldDefinition + score: EvaluatorOutputFieldDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class PublicEvaluatorOutputDefinition_Boolean(UniversalBaseModel): + """ + Evaluator output definition returned by the public API. + + This response always includes `dataType` and never includes an internal output-definition `version`. + Legacy stored evaluator definitions are normalized into this shape before they are returned. + + Use this response shape when deciding how to interpret future evaluation scores: + - `NUMERIC`: expect numeric score values + - `BOOLEAN`: expect `true` / `false` + - `CATEGORICAL`: expect one or more values from `score.categories` + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputFieldDefinition, + PublicEvaluatorOutputDefinition_Numeric, + ) + + PublicEvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), + ) + """ + + data_type: typing_extensions.Annotated[ + typing.Literal["BOOLEAN"], FieldMetadata(alias="dataType") + ] = "BOOLEAN" + reasoning: EvaluatorOutputFieldDefinition + score: EvaluatorOutputFieldDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class PublicEvaluatorOutputDefinition_Categorical(UniversalBaseModel): + """ + Evaluator output definition returned by the public API. + + This response always includes `dataType` and never includes an internal output-definition `version`. + Legacy stored evaluator definitions are normalized into this shape before they are returned. + + Use this response shape when deciding how to interpret future evaluation scores: + - `NUMERIC`: expect numeric score values + - `BOOLEAN`: expect `true` / `false` + - `CATEGORICAL`: expect one or more values from `score.categories` + + Examples + -------- + from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputFieldDefinition, + PublicEvaluatorOutputDefinition_Numeric, + ) + + PublicEvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), + ) + """ + + data_type: typing_extensions.Annotated[ + typing.Literal["CATEGORICAL"], FieldMetadata(alias="dataType") + ] = "CATEGORICAL" + reasoning: EvaluatorOutputFieldDefinition + score: PublicCategoricalEvaluatorOutputScoreDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +""" +from langfuse.unstable.commons import ( + EvaluatorOutputDataType, + EvaluatorOutputFieldDefinition, + PublicEvaluatorOutputDefinition_Numeric, +) + +PublicEvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the answer is correct or incorrect.", + ), + score=EvaluatorOutputFieldDefinition( + description="Return a score between 0 and 1.", + ), +) +""" +PublicEvaluatorOutputDefinition = typing_extensions.Annotated[ + typing.Union[ + PublicEvaluatorOutputDefinition_Numeric, + PublicEvaluatorOutputDefinition_Boolean, + PublicEvaluatorOutputDefinition_Categorical, + ], + pydantic.Field(discriminator="data_type"), +] diff --git a/langfuse/api/unstable/commons/types/public_numeric_evaluator_output_definition.py b/langfuse/api/unstable/commons/types/public_numeric_evaluator_output_definition.py new file mode 100644 index 000000000..68987d2ff --- /dev/null +++ b/langfuse/api/unstable/commons/types/public_numeric_evaluator_output_definition.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .evaluator_output_data_type import EvaluatorOutputDataType +from .evaluator_output_field_definition import EvaluatorOutputFieldDefinition + + +class PublicNumericEvaluatorOutputDefinition(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + EvaluatorOutputDataType, FieldMetadata(alias="dataType") + ] = pydantic.Field() + """ + Always `NUMERIC`. + """ + + reasoning: EvaluatorOutputFieldDefinition + score: EvaluatorOutputFieldDefinition + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/string_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/string_evaluation_rule_filter.py new file mode 100644 index 000000000..bd9332092 --- /dev/null +++ b/langfuse/api/unstable/commons/types/string_evaluation_rule_filter.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_string_filter_operator import EvaluationRuleStringFilterOperator + + +class StringEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. + """ + + operator: EvaluationRuleStringFilterOperator + value: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/string_object_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/string_object_evaluation_rule_filter.py new file mode 100644 index 000000000..6c287aad6 --- /dev/null +++ b/langfuse/api/unstable/commons/types/string_object_evaluation_rule_filter.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_string_filter_operator import EvaluationRuleStringFilterOperator + + +class StringObjectEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Object-valued column to filter on. In the unstable public API this is currently `metadata`. + """ + + key: str = pydantic.Field() + """ + Top-level key inside the object-valued column to filter on. + """ + + operator: EvaluationRuleStringFilterOperator + value: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/commons/types/string_options_evaluation_rule_filter.py b/langfuse/api/unstable/commons/types/string_options_evaluation_rule_filter.py new file mode 100644 index 000000000..a830e5ad9 --- /dev/null +++ b/langfuse/api/unstable/commons/types/string_options_evaluation_rule_filter.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .evaluation_rule_options_filter_operator import EvaluationRuleOptionsFilterOperator + + +class StringOptionsEvaluationRuleFilter(UniversalBaseModel): + column: str = pydantic.Field() + """ + Column to filter on. + """ + + operator: EvaluationRuleOptionsFilterOperator + value: typing.List[str] = pydantic.Field() + """ + One or more allowed string values. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/errors/__init__.py b/langfuse/api/unstable/errors/__init__.py new file mode 100644 index 000000000..42f230c41 --- /dev/null +++ b/langfuse/api/unstable/errors/__init__.py @@ -0,0 +1,84 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + PublicApiError, + PublicApiErrorCode, + PublicApiErrorDetails, + PublicApiValidationIssue, + ) + from .errors import ( + AccessDeniedError, + BadRequestError, + ConflictError, + InternalServerError, + MethodNotAllowedError, + NotFoundError, + TooManyRequestsError, + UnauthorizedError, + UnprocessableContentError, + ) +_dynamic_imports: typing.Dict[str, str] = { + "AccessDeniedError": ".errors", + "BadRequestError": ".errors", + "ConflictError": ".errors", + "InternalServerError": ".errors", + "MethodNotAllowedError": ".errors", + "NotFoundError": ".errors", + "PublicApiError": ".types", + "PublicApiErrorCode": ".types", + "PublicApiErrorDetails": ".types", + "PublicApiValidationIssue": ".types", + "TooManyRequestsError": ".errors", + "UnauthorizedError": ".errors", + "UnprocessableContentError": ".errors", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccessDeniedError", + "BadRequestError", + "ConflictError", + "InternalServerError", + "MethodNotAllowedError", + "NotFoundError", + "PublicApiError", + "PublicApiErrorCode", + "PublicApiErrorDetails", + "PublicApiValidationIssue", + "TooManyRequestsError", + "UnauthorizedError", + "UnprocessableContentError", +] diff --git a/langfuse/api/unstable/errors/errors/__init__.py b/langfuse/api/unstable/errors/errors/__init__.py new file mode 100644 index 000000000..510e3beb1 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/__init__.py @@ -0,0 +1,68 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .access_denied_error import AccessDeniedError + from .bad_request_error import BadRequestError + from .conflict_error import ConflictError + from .internal_server_error import InternalServerError + from .method_not_allowed_error import MethodNotAllowedError + from .not_found_error import NotFoundError + from .too_many_requests_error import TooManyRequestsError + from .unauthorized_error import UnauthorizedError + from .unprocessable_content_error import UnprocessableContentError +_dynamic_imports: typing.Dict[str, str] = { + "AccessDeniedError": ".access_denied_error", + "BadRequestError": ".bad_request_error", + "ConflictError": ".conflict_error", + "InternalServerError": ".internal_server_error", + "MethodNotAllowedError": ".method_not_allowed_error", + "NotFoundError": ".not_found_error", + "TooManyRequestsError": ".too_many_requests_error", + "UnauthorizedError": ".unauthorized_error", + "UnprocessableContentError": ".unprocessable_content_error", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccessDeniedError", + "BadRequestError", + "ConflictError", + "InternalServerError", + "MethodNotAllowedError", + "NotFoundError", + "TooManyRequestsError", + "UnauthorizedError", + "UnprocessableContentError", +] diff --git a/langfuse/api/unstable/errors/errors/access_denied_error.py b/langfuse/api/unstable/errors/errors/access_denied_error.py new file mode 100644 index 000000000..6e07b4c79 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/access_denied_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class AccessDeniedError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=403, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/bad_request_error.py b/langfuse/api/unstable/errors/errors/bad_request_error.py new file mode 100644 index 000000000..7ba4c1a00 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/bad_request_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class BadRequestError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=400, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/conflict_error.py b/langfuse/api/unstable/errors/errors/conflict_error.py new file mode 100644 index 000000000..3630eec67 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/conflict_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class ConflictError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=409, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/internal_server_error.py b/langfuse/api/unstable/errors/errors/internal_server_error.py new file mode 100644 index 000000000..5921a86ae --- /dev/null +++ b/langfuse/api/unstable/errors/errors/internal_server_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class InternalServerError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=500, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/method_not_allowed_error.py b/langfuse/api/unstable/errors/errors/method_not_allowed_error.py new file mode 100644 index 000000000..547598806 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/method_not_allowed_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class MethodNotAllowedError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=405, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/not_found_error.py b/langfuse/api/unstable/errors/errors/not_found_error.py new file mode 100644 index 000000000..1b65b230e --- /dev/null +++ b/langfuse/api/unstable/errors/errors/not_found_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class NotFoundError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=404, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/too_many_requests_error.py b/langfuse/api/unstable/errors/errors/too_many_requests_error.py new file mode 100644 index 000000000..2a8345bc7 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/too_many_requests_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class TooManyRequestsError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=429, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/unauthorized_error.py b/langfuse/api/unstable/errors/errors/unauthorized_error.py new file mode 100644 index 000000000..84d847643 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/unauthorized_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class UnauthorizedError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=401, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/errors/unprocessable_content_error.py b/langfuse/api/unstable/errors/errors/unprocessable_content_error.py new file mode 100644 index 000000000..a701ef9c5 --- /dev/null +++ b/langfuse/api/unstable/errors/errors/unprocessable_content_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core.api_error import ApiError +from ..types.public_api_error import PublicApiError + + +class UnprocessableContentError(ApiError): + def __init__( + self, + body: PublicApiError, + headers: typing.Optional[typing.Dict[str, str]] = None, + ): + super().__init__(status_code=422, headers=headers, body=body) diff --git a/langfuse/api/unstable/errors/types/__init__.py b/langfuse/api/unstable/errors/types/__init__.py new file mode 100644 index 000000000..fd016304e --- /dev/null +++ b/langfuse/api/unstable/errors/types/__init__.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .public_api_error import PublicApiError + from .public_api_error_code import PublicApiErrorCode + from .public_api_error_details import PublicApiErrorDetails + from .public_api_validation_issue import PublicApiValidationIssue +_dynamic_imports: typing.Dict[str, str] = { + "PublicApiError": ".public_api_error", + "PublicApiErrorCode": ".public_api_error_code", + "PublicApiErrorDetails": ".public_api_error_details", + "PublicApiValidationIssue": ".public_api_validation_issue", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "PublicApiError", + "PublicApiErrorCode", + "PublicApiErrorDetails", + "PublicApiValidationIssue", +] diff --git a/langfuse/api/unstable/errors/types/public_api_error.py b/langfuse/api/unstable/errors/types/public_api_error.py new file mode 100644 index 000000000..5d1384e7c --- /dev/null +++ b/langfuse/api/unstable/errors/types/public_api_error.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .public_api_error_code import PublicApiErrorCode +from .public_api_error_details import PublicApiErrorDetails + + +class PublicApiError(UniversalBaseModel): + """ + Standard error envelope for the unstable evaluators API. + + Response handling guidance: + - Use the HTTP status code for the broad class of failure. + - Use `code` for precise branching in SDKs, CLIs, or agents. + - Inspect `details` for field-level validation context such as invalid filter values, malformed JSONPath expressions, or missing variable mappings. + - Retry only after fixing the specific issue described by `code` and `details`. + + Examples + -------- + from langfuse.unstable.errors import ( + PublicApiError, + PublicApiErrorCode, + PublicApiErrorDetails, + ) + + PublicApiError( + message='Filter column "type" contains unsupported value(s): INVALID', + code=PublicApiErrorCode.INVALID_FILTER_VALUE, + details=PublicApiErrorDetails( + field="filter[0].value", + column="type", + invalid_values=["INVALID"], + allowed_values=["GENERATION", "SPAN", "EVENT"], + ), + ) + """ + + message: str = pydantic.Field() + """ + Human-readable description of the failure. + """ + + code: PublicApiErrorCode = pydantic.Field() + """ + Stable machine-readable error code. + """ + + details: typing.Optional[PublicApiErrorDetails] = pydantic.Field(default=None) + """ + Optional structured error context. Inspect the populated fields based on `code`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/errors/types/public_api_error_code.py b/langfuse/api/unstable/errors/types/public_api_error_code.py new file mode 100644 index 000000000..fe8f67f83 --- /dev/null +++ b/langfuse/api/unstable/errors/types/public_api_error_code.py @@ -0,0 +1,93 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class PublicApiErrorCode(enum.StrEnum): + """ + Machine-readable error code returned by the unstable evaluators API. + + SDKs, CLIs, and agents should branch on `code` rather than parsing the human-readable `message`. + The HTTP status still indicates the broad error class, while `code` gives the specific failure reason. + """ + + AUTHENTICATION_FAILED = "authentication_failed" + ACCESS_DENIED = "access_denied" + INVALID_REQUEST = "invalid_request" + INVALID_QUERY = "invalid_query" + INVALID_BODY = "invalid_body" + INVALID_FILTER_VALUE = "invalid_filter_value" + INVALID_JSON_PATH = "invalid_json_path" + INVALID_VARIABLE_MAPPING = "invalid_variable_mapping" + MISSING_VARIABLE_MAPPING = "missing_variable_mapping" + DUPLICATE_VARIABLE_MAPPING = "duplicate_variable_mapping" + RESOURCE_NOT_FOUND = "resource_not_found" + NAME_CONFLICT = "name_conflict" + EVALUATOR_PREFLIGHT_FAILED = "evaluator_preflight_failed" + CONFLICT = "conflict" + UNPROCESSABLE_CONTENT = "unprocessable_content" + RATE_LIMITED = "rate_limited" + METHOD_NOT_ALLOWED = "method_not_allowed" + INTERNAL_ERROR = "internal_error" + + def visit( + self, + authentication_failed: typing.Callable[[], T_Result], + access_denied: typing.Callable[[], T_Result], + invalid_request: typing.Callable[[], T_Result], + invalid_query: typing.Callable[[], T_Result], + invalid_body: typing.Callable[[], T_Result], + invalid_filter_value: typing.Callable[[], T_Result], + invalid_json_path: typing.Callable[[], T_Result], + invalid_variable_mapping: typing.Callable[[], T_Result], + missing_variable_mapping: typing.Callable[[], T_Result], + duplicate_variable_mapping: typing.Callable[[], T_Result], + resource_not_found: typing.Callable[[], T_Result], + name_conflict: typing.Callable[[], T_Result], + evaluator_preflight_failed: typing.Callable[[], T_Result], + conflict: typing.Callable[[], T_Result], + unprocessable_content: typing.Callable[[], T_Result], + rate_limited: typing.Callable[[], T_Result], + method_not_allowed: typing.Callable[[], T_Result], + internal_error: typing.Callable[[], T_Result], + ) -> T_Result: + if self is PublicApiErrorCode.AUTHENTICATION_FAILED: + return authentication_failed() + if self is PublicApiErrorCode.ACCESS_DENIED: + return access_denied() + if self is PublicApiErrorCode.INVALID_REQUEST: + return invalid_request() + if self is PublicApiErrorCode.INVALID_QUERY: + return invalid_query() + if self is PublicApiErrorCode.INVALID_BODY: + return invalid_body() + if self is PublicApiErrorCode.INVALID_FILTER_VALUE: + return invalid_filter_value() + if self is PublicApiErrorCode.INVALID_JSON_PATH: + return invalid_json_path() + if self is PublicApiErrorCode.INVALID_VARIABLE_MAPPING: + return invalid_variable_mapping() + if self is PublicApiErrorCode.MISSING_VARIABLE_MAPPING: + return missing_variable_mapping() + if self is PublicApiErrorCode.DUPLICATE_VARIABLE_MAPPING: + return duplicate_variable_mapping() + if self is PublicApiErrorCode.RESOURCE_NOT_FOUND: + return resource_not_found() + if self is PublicApiErrorCode.NAME_CONFLICT: + return name_conflict() + if self is PublicApiErrorCode.EVALUATOR_PREFLIGHT_FAILED: + return evaluator_preflight_failed() + if self is PublicApiErrorCode.CONFLICT: + return conflict() + if self is PublicApiErrorCode.UNPROCESSABLE_CONTENT: + return unprocessable_content() + if self is PublicApiErrorCode.RATE_LIMITED: + return rate_limited() + if self is PublicApiErrorCode.METHOD_NOT_ALLOWED: + return method_not_allowed() + if self is PublicApiErrorCode.INTERNAL_ERROR: + return internal_error() diff --git a/langfuse/api/unstable/errors/types/public_api_error_details.py b/langfuse/api/unstable/errors/types/public_api_error_details.py new file mode 100644 index 000000000..803378164 --- /dev/null +++ b/langfuse/api/unstable/errors/types/public_api_error_details.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .public_api_validation_issue import PublicApiValidationIssue + + +class PublicApiErrorDetails(UniversalBaseModel): + """ + Optional structured context attached to an unstable-evals error. + + The populated fields depend on the error `code`: + - request parsing failures populate `issues` + - filter validation failures populate `field`, `column`, `invalidValues`, and `allowedValues` + - variable mapping failures populate `field`, `variable`, or `variables` + - JSONPath validation failures populate `field`, `variable`, and `value` + - evaluator preflight failures populate `evaluatorName`, `provider`, and `model` + - rate limiting populates `retryAfterSeconds`, `limit`, `remaining`, and `resetAt` + """ + + issues: typing.Optional[typing.List[PublicApiValidationIssue]] = pydantic.Field( + default=None + ) + """ + Validation issues for malformed request bodies or query parameters. + """ + + field: typing.Optional[str] = pydantic.Field(default=None) + """ + Path-like reference to the failing field, for example `mapping[1].jsonPath`. + """ + + column: typing.Optional[str] = pydantic.Field(default=None) + """ + Filter column that failed validation. + """ + + invalid_values: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="invalidValues") + ] = pydantic.Field(default=None) + """ + Unsupported values supplied by the caller. + """ + + allowed_values: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="allowedValues") + ] = pydantic.Field(default=None) + """ + Allowed values for the failing filter column. + """ + + variable: typing.Optional[str] = pydantic.Field(default=None) + """ + Evaluator variable involved in the failure. + """ + + variables: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Multiple evaluator variables involved in the failure, for example missing mappings. + """ + + value: typing.Optional[str] = pydantic.Field(default=None) + """ + Raw invalid value supplied by the caller. + """ + + evaluator_name: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="evaluatorName") + ] = pydantic.Field(default=None) + """ + Evaluator name used during preflight validation. + """ + + provider: typing.Optional[str] = pydantic.Field(default=None) + """ + Provider resolved during evaluator preflight, if any. + """ + + model: typing.Optional[str] = pydantic.Field(default=None) + """ + Model resolved during evaluator preflight, if any. + """ + + retry_after_seconds: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="retryAfterSeconds") + ] = pydantic.Field(default=None) + """ + Suggested retry delay for rate-limited requests. + """ + + limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Numeric limit associated with the failure, for example the active evaluation-rule cap or the current rate-limit window. + """ + + remaining: typing.Optional[int] = pydantic.Field(default=None) + """ + Remaining requests in the current rate-limit window. + """ + + reset_at: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="resetAt") + ] = pydantic.Field(default=None) + """ + ISO-8601 timestamp when the current rate-limit window resets. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/errors/types/public_api_validation_issue.py b/langfuse/api/unstable/errors/types/public_api_validation_issue.py new file mode 100644 index 000000000..877d0376a --- /dev/null +++ b/langfuse/api/unstable/errors/types/public_api_validation_issue.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class PublicApiValidationIssue(UniversalBaseModel): + """ + One validation issue returned for malformed request bodies or query parameters. + + This mirrors the most important parts of a Zod issue: a machine-readable `code`, + a human-readable `message`, and a structured `path`. + """ + + code: str = pydantic.Field() + """ + Machine-readable validation issue code emitted by the server validator. + """ + + message: str = pydantic.Field() + """ + Human-readable explanation of the validation failure. + """ + + path: typing.List[typing.Any] = pydantic.Field() + """ + Path to the invalid field, for example `["mapping", 0, "jsonPath"]`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/__init__.py b/langfuse/api/unstable/evaluation_rules/__init__.py new file mode 100644 index 000000000..f0c007231 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/__init__.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateEvaluationRuleRequest, + DeleteEvaluationRuleResponse, + EvaluationRule, + EvaluationRuleEvaluator, + EvaluationRuleEvaluatorReference, + EvaluationRules, + UpdateEvaluationRuleRequest, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreateEvaluationRuleRequest": ".types", + "DeleteEvaluationRuleResponse": ".types", + "EvaluationRule": ".types", + "EvaluationRuleEvaluator": ".types", + "EvaluationRuleEvaluatorReference": ".types", + "EvaluationRules": ".types", + "UpdateEvaluationRuleRequest": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateEvaluationRuleRequest", + "DeleteEvaluationRuleResponse", + "EvaluationRule", + "EvaluationRuleEvaluator", + "EvaluationRuleEvaluatorReference", + "EvaluationRules", + "UpdateEvaluationRuleRequest", +] diff --git a/langfuse/api/unstable/evaluation_rules/client.py b/langfuse/api/unstable/evaluation_rules/client.py new file mode 100644 index 000000000..20e56e6c3 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/client.py @@ -0,0 +1,859 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ..commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ..commons.types.evaluation_rule_mapping import EvaluationRuleMapping +from ..commons.types.evaluation_rule_target import EvaluationRuleTarget +from .raw_client import AsyncRawEvaluationRulesClient, RawEvaluationRulesClient +from .types.delete_evaluation_rule_response import DeleteEvaluationRuleResponse +from .types.evaluation_rule import EvaluationRule +from .types.evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference +from .types.evaluation_rules import EvaluationRules + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class EvaluationRulesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawEvaluationRulesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawEvaluationRulesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawEvaluationRulesClient + """ + return self._raw_client + + def create( + self, + *, + name: str, + evaluator: EvaluationRuleEvaluatorReference, + target: EvaluationRuleTarget, + enabled: bool, + mapping: typing.Sequence[EvaluationRuleMapping], + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRule: + """ + Create an evaluation rule. + + An evaluation rule defines **what** incoming data should be evaluated and **how prompt variables should be populated** from that data. + + Use this resource after choosing an evaluator from the evaluator endpoints. + + Key rules: + - `name` must be unique within the project for public evaluation rules + - `target` must be `observation` or `experiment` + - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints + - Langfuse resolves that family to its latest version before saving the evaluation rule + - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` + - every evaluator prompt variable must be mapped exactly once + - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run + - at most 50 evaluation rules can be effectively active in one project at the same time + + If an evaluation rule with the same `name` already exists in the project, the API returns `409`. + In that case, update the existing resource with `PATCH /api/public/unstable/evaluation-rules/{evaluationRuleId}` instead of creating a second one. + + If enabling this resource would exceed the 50-active limit, the API also returns `409`. + In that case, disable or pause another active evaluation rule before enabling a new one. + + Current scope: + - evaluation rules are live-ingestion rules only + - they do not trigger historical backfills + + Recovery guidance: + - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` + - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response + - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable + - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_json_path`: remove or correct the `jsonPath` + - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. + + Parameters + ---------- + name : str + Human-readable deployment name. + + evaluator : EvaluationRuleEvaluatorReference + Evaluator family to use. + + Use `name` and `scope` from the evaluator endpoints. + Langfuse resolves that family to its latest version before saving the rule. + + target : EvaluationRuleTarget + Target object type to evaluate. + + enabled : bool + Whether the deployment should be active immediately after creation. + + mapping : typing.Sequence[EvaluationRuleMapping] + Required variable mappings. + + Every evaluator variable must appear exactly once. + Build this list from the evaluator `variables` array returned by the evaluator endpoints. + + sampling : typing.Optional[float] + Optional sampling fraction. Defaults to `1`. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRule + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleMapping, + EvaluationRuleMappingSource, + EvaluationRuleOptionsFilterOperator, + EvaluationRuleTarget, + EvaluatorScope, + ) + from langfuse.unstable.evaluation_rules import EvaluationRuleEvaluatorReference + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluation_rules.create( + name="answer-correctness-live", + evaluator=EvaluationRuleEvaluatorReference( + name="answer-correctness", + scope=EvaluatorScope.PROJECT, + ), + target=EvaluationRuleTarget.OBSERVATION, + enabled=True, + sampling=1.0, + filter=[ + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + ], + mapping=[ + EvaluationRuleMapping( + variable="input", + source=EvaluationRuleMappingSource.INPUT, + ), + EvaluationRuleMapping( + variable="output", + source=EvaluationRuleMappingSource.OUTPUT, + ), + ], + ) + """ + _response = self._raw_client.create( + name=name, + evaluator=evaluator, + target=target, + enabled=enabled, + mapping=mapping, + sampling=sampling, + filter=filter, + request_options=request_options, + ) + return _response.data + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRules: + """ + List evaluation rules in the authenticated project. + + Each item describes one live evaluation rule and its effective runtime status. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRules + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluation_rules.list() + """ + _response = self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRule: + """ + Get one evaluation rule by its identifier. + + Use this endpoint to inspect the current evaluator, target, mapping, filters, and effective runtime status. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier returned by the evaluation rule endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRule + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluation_rules.get( + evaluation_rule_id="evaluationRuleId", + ) + """ + _response = self._raw_client.get( + evaluation_rule_id, request_options=request_options + ) + return _response.data + + def update( + self, + evaluation_rule_id: str, + *, + name: typing.Optional[str] = OMIT, + evaluator: typing.Optional[EvaluationRuleEvaluatorReference] = OMIT, + target: typing.Optional[EvaluationRuleTarget] = OMIT, + enabled: typing.Optional[bool] = OMIT, + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + mapping: typing.Optional[typing.Sequence[EvaluationRuleMapping]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRule: + """ + Update an evaluation rule. + + Typical uses: + - enable or disable live execution + - switch to another evaluator + - adjust sampling + - change filters + - update variable mappings + + Important behavior: + - provide only the fields you want to change + - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving + - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration + - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run + - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` + + Recovery guidance: + - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + name : typing.Optional[str] + Updated deployment name. + + evaluator : typing.Optional[EvaluationRuleEvaluatorReference] + Updated evaluator family. + + Langfuse resolves the provided evaluator family to its latest version before saving the rule. + + target : typing.Optional[EvaluationRuleTarget] + Updated target object type. + + enabled : typing.Optional[bool] + Updated desired enabled state. + + sampling : typing.Optional[float] + Updated sampling fraction. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Updated filter list. + + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] + Updated variable mappings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRule + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluation_rules.update( + evaluation_rule_id="evaluationRuleId", + ) + """ + _response = self._raw_client.update( + evaluation_rule_id, + name=name, + evaluator=evaluator, + target=target, + enabled=enabled, + sampling=sampling, + filter=filter, + mapping=mapping, + request_options=request_options, + ) + return _response.data + + def delete( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteEvaluationRuleResponse: + """ + Delete an evaluation rule. + + This removes the live-ingestion rule only. It does not delete the referenced evaluator. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteEvaluationRuleResponse + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluation_rules.delete( + evaluation_rule_id="evaluationRuleId", + ) + """ + _response = self._raw_client.delete( + evaluation_rule_id, request_options=request_options + ) + return _response.data + + +class AsyncEvaluationRulesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawEvaluationRulesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawEvaluationRulesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawEvaluationRulesClient + """ + return self._raw_client + + async def create( + self, + *, + name: str, + evaluator: EvaluationRuleEvaluatorReference, + target: EvaluationRuleTarget, + enabled: bool, + mapping: typing.Sequence[EvaluationRuleMapping], + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRule: + """ + Create an evaluation rule. + + An evaluation rule defines **what** incoming data should be evaluated and **how prompt variables should be populated** from that data. + + Use this resource after choosing an evaluator from the evaluator endpoints. + + Key rules: + - `name` must be unique within the project for public evaluation rules + - `target` must be `observation` or `experiment` + - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints + - Langfuse resolves that family to its latest version before saving the evaluation rule + - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` + - every evaluator prompt variable must be mapped exactly once + - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run + - at most 50 evaluation rules can be effectively active in one project at the same time + + If an evaluation rule with the same `name` already exists in the project, the API returns `409`. + In that case, update the existing resource with `PATCH /api/public/unstable/evaluation-rules/{evaluationRuleId}` instead of creating a second one. + + If enabling this resource would exceed the 50-active limit, the API also returns `409`. + In that case, disable or pause another active evaluation rule before enabling a new one. + + Current scope: + - evaluation rules are live-ingestion rules only + - they do not trigger historical backfills + + Recovery guidance: + - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` + - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response + - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable + - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_json_path`: remove or correct the `jsonPath` + - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. + + Parameters + ---------- + name : str + Human-readable deployment name. + + evaluator : EvaluationRuleEvaluatorReference + Evaluator family to use. + + Use `name` and `scope` from the evaluator endpoints. + Langfuse resolves that family to its latest version before saving the rule. + + target : EvaluationRuleTarget + Target object type to evaluate. + + enabled : bool + Whether the deployment should be active immediately after creation. + + mapping : typing.Sequence[EvaluationRuleMapping] + Required variable mappings. + + Every evaluator variable must appear exactly once. + Build this list from the evaluator `variables` array returned by the evaluator endpoints. + + sampling : typing.Optional[float] + Optional sampling fraction. Defaults to `1`. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRule + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleMapping, + EvaluationRuleMappingSource, + EvaluationRuleOptionsFilterOperator, + EvaluationRuleTarget, + EvaluatorScope, + ) + from langfuse.unstable.evaluation_rules import EvaluationRuleEvaluatorReference + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluation_rules.create( + name="answer-correctness-live", + evaluator=EvaluationRuleEvaluatorReference( + name="answer-correctness", + scope=EvaluatorScope.PROJECT, + ), + target=EvaluationRuleTarget.OBSERVATION, + enabled=True, + sampling=1.0, + filter=[ + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + ], + mapping=[ + EvaluationRuleMapping( + variable="input", + source=EvaluationRuleMappingSource.INPUT, + ), + EvaluationRuleMapping( + variable="output", + source=EvaluationRuleMappingSource.OUTPUT, + ), + ], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + evaluator=evaluator, + target=target, + enabled=enabled, + mapping=mapping, + sampling=sampling, + filter=filter, + request_options=request_options, + ) + return _response.data + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRules: + """ + List evaluation rules in the authenticated project. + + Each item describes one live evaluation rule and its effective runtime status. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRules + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluation_rules.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRule: + """ + Get one evaluation rule by its identifier. + + Use this endpoint to inspect the current evaluator, target, mapping, filters, and effective runtime status. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier returned by the evaluation rule endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRule + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluation_rules.get( + evaluation_rule_id="evaluationRuleId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + evaluation_rule_id, request_options=request_options + ) + return _response.data + + async def update( + self, + evaluation_rule_id: str, + *, + name: typing.Optional[str] = OMIT, + evaluator: typing.Optional[EvaluationRuleEvaluatorReference] = OMIT, + target: typing.Optional[EvaluationRuleTarget] = OMIT, + enabled: typing.Optional[bool] = OMIT, + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + mapping: typing.Optional[typing.Sequence[EvaluationRuleMapping]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> EvaluationRule: + """ + Update an evaluation rule. + + Typical uses: + - enable or disable live execution + - switch to another evaluator + - adjust sampling + - change filters + - update variable mappings + + Important behavior: + - provide only the fields you want to change + - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving + - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration + - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run + - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` + + Recovery guidance: + - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + name : typing.Optional[str] + Updated deployment name. + + evaluator : typing.Optional[EvaluationRuleEvaluatorReference] + Updated evaluator family. + + Langfuse resolves the provided evaluator family to its latest version before saving the rule. + + target : typing.Optional[EvaluationRuleTarget] + Updated target object type. + + enabled : typing.Optional[bool] + Updated desired enabled state. + + sampling : typing.Optional[float] + Updated sampling fraction. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Updated filter list. + + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] + Updated variable mappings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + EvaluationRule + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluation_rules.update( + evaluation_rule_id="evaluationRuleId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + evaluation_rule_id, + name=name, + evaluator=evaluator, + target=target, + enabled=enabled, + sampling=sampling, + filter=filter, + mapping=mapping, + request_options=request_options, + ) + return _response.data + + async def delete( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteEvaluationRuleResponse: + """ + Delete an evaluation rule. + + This removes the live-ingestion rule only. It does not delete the referenced evaluator. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteEvaluationRuleResponse + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluation_rules.delete( + evaluation_rule_id="evaluationRuleId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete( + evaluation_rule_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/unstable/evaluation_rules/raw_client.py b/langfuse/api/unstable/evaluation_rules/raw_client.py new file mode 100644 index 000000000..f99aba663 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/raw_client.py @@ -0,0 +1,2271 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...commons.errors.access_denied_error import ( + AccessDeniedError as commons_errors_access_denied_error_AccessDeniedError, +) +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import ( + MethodNotAllowedError as commons_errors_method_not_allowed_error_MethodNotAllowedError, +) +from ...commons.errors.not_found_error import ( + NotFoundError as commons_errors_not_found_error_NotFoundError, +) +from ...commons.errors.unauthorized_error import ( + UnauthorizedError as commons_errors_unauthorized_error_UnauthorizedError, +) +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata +from ..commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ..commons.types.evaluation_rule_mapping import EvaluationRuleMapping +from ..commons.types.evaluation_rule_target import EvaluationRuleTarget +from ..errors.errors.access_denied_error import ( + AccessDeniedError as unstable_errors_errors_access_denied_error_AccessDeniedError, +) +from ..errors.errors.bad_request_error import BadRequestError +from ..errors.errors.conflict_error import ConflictError +from ..errors.errors.internal_server_error import InternalServerError +from ..errors.errors.method_not_allowed_error import ( + MethodNotAllowedError as unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError, +) +from ..errors.errors.not_found_error import ( + NotFoundError as unstable_errors_errors_not_found_error_NotFoundError, +) +from ..errors.errors.too_many_requests_error import TooManyRequestsError +from ..errors.errors.unauthorized_error import ( + UnauthorizedError as unstable_errors_errors_unauthorized_error_UnauthorizedError, +) +from ..errors.errors.unprocessable_content_error import UnprocessableContentError +from ..errors.types.public_api_error import PublicApiError +from .types.delete_evaluation_rule_response import DeleteEvaluationRuleResponse +from .types.evaluation_rule import EvaluationRule +from .types.evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference +from .types.evaluation_rules import EvaluationRules + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawEvaluationRulesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + name: str, + evaluator: EvaluationRuleEvaluatorReference, + target: EvaluationRuleTarget, + enabled: bool, + mapping: typing.Sequence[EvaluationRuleMapping], + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EvaluationRule]: + """ + Create an evaluation rule. + + An evaluation rule defines **what** incoming data should be evaluated and **how prompt variables should be populated** from that data. + + Use this resource after choosing an evaluator from the evaluator endpoints. + + Key rules: + - `name` must be unique within the project for public evaluation rules + - `target` must be `observation` or `experiment` + - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints + - Langfuse resolves that family to its latest version before saving the evaluation rule + - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` + - every evaluator prompt variable must be mapped exactly once + - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run + - at most 50 evaluation rules can be effectively active in one project at the same time + + If an evaluation rule with the same `name` already exists in the project, the API returns `409`. + In that case, update the existing resource with `PATCH /api/public/unstable/evaluation-rules/{evaluationRuleId}` instead of creating a second one. + + If enabling this resource would exceed the 50-active limit, the API also returns `409`. + In that case, disable or pause another active evaluation rule before enabling a new one. + + Current scope: + - evaluation rules are live-ingestion rules only + - they do not trigger historical backfills + + Recovery guidance: + - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` + - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response + - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable + - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_json_path`: remove or correct the `jsonPath` + - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. + + Parameters + ---------- + name : str + Human-readable deployment name. + + evaluator : EvaluationRuleEvaluatorReference + Evaluator family to use. + + Use `name` and `scope` from the evaluator endpoints. + Langfuse resolves that family to its latest version before saving the rule. + + target : EvaluationRuleTarget + Target object type to evaluate. + + enabled : bool + Whether the deployment should be active immediately after creation. + + mapping : typing.Sequence[EvaluationRuleMapping] + Required variable mappings. + + Every evaluator variable must appear exactly once. + Build this list from the evaluator `variables` array returned by the evaluator endpoints. + + sampling : typing.Optional[float] + Optional sampling fraction. Defaults to `1`. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EvaluationRule] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluation-rules", + method="POST", + json={ + "name": name, + "evaluator": convert_and_respect_annotation_metadata( + object_=evaluator, + annotation=EvaluationRuleEvaluatorReference, + direction="write", + ), + "target": target, + "enabled": enabled, + "sampling": sampling, + "filter": convert_and_respect_annotation_metadata( + object_=filter, + annotation=typing.Sequence[EvaluationRuleFilter], + direction="write", + ), + "mapping": convert_and_respect_annotation_metadata( + object_=mapping, + annotation=typing.Sequence[EvaluationRuleMapping], + direction="write", + ), + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRule, + parse_obj_as( + type_=EvaluationRule, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableContentError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EvaluationRules]: + """ + List evaluation rules in the authenticated project. + + Each item describes one live evaluation rule and its effective runtime status. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EvaluationRules] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluation-rules", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRules, + parse_obj_as( + type_=EvaluationRules, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EvaluationRule]: + """ + Get one evaluation rule by its identifier. + + Use this endpoint to inspect the current evaluator, target, mapping, filters, and effective runtime status. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier returned by the evaluation rule endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EvaluationRule] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluation-rules/{jsonable_encoder(evaluation_rule_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRule, + parse_obj_as( + type_=EvaluationRule, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def update( + self, + evaluation_rule_id: str, + *, + name: typing.Optional[str] = OMIT, + evaluator: typing.Optional[EvaluationRuleEvaluatorReference] = OMIT, + target: typing.Optional[EvaluationRuleTarget] = OMIT, + enabled: typing.Optional[bool] = OMIT, + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + mapping: typing.Optional[typing.Sequence[EvaluationRuleMapping]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[EvaluationRule]: + """ + Update an evaluation rule. + + Typical uses: + - enable or disable live execution + - switch to another evaluator + - adjust sampling + - change filters + - update variable mappings + + Important behavior: + - provide only the fields you want to change + - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving + - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration + - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run + - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` + + Recovery guidance: + - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + name : typing.Optional[str] + Updated deployment name. + + evaluator : typing.Optional[EvaluationRuleEvaluatorReference] + Updated evaluator family. + + Langfuse resolves the provided evaluator family to its latest version before saving the rule. + + target : typing.Optional[EvaluationRuleTarget] + Updated target object type. + + enabled : typing.Optional[bool] + Updated desired enabled state. + + sampling : typing.Optional[float] + Updated sampling fraction. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Updated filter list. + + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] + Updated variable mappings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[EvaluationRule] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluation-rules/{jsonable_encoder(evaluation_rule_id)}", + method="PATCH", + json={ + "name": name, + "evaluator": convert_and_respect_annotation_metadata( + object_=evaluator, + annotation=EvaluationRuleEvaluatorReference, + direction="write", + ), + "target": target, + "enabled": enabled, + "sampling": sampling, + "filter": convert_and_respect_annotation_metadata( + object_=filter, + annotation=typing.Sequence[EvaluationRuleFilter], + direction="write", + ), + "mapping": convert_and_respect_annotation_metadata( + object_=mapping, + annotation=typing.Sequence[EvaluationRuleMapping], + direction="write", + ), + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRule, + parse_obj_as( + type_=EvaluationRule, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableContentError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def delete( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DeleteEvaluationRuleResponse]: + """ + Delete an evaluation rule. + + This removes the live-ingestion rule only. It does not delete the referenced evaluator. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteEvaluationRuleResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluation-rules/{jsonable_encoder(evaluation_rule_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteEvaluationRuleResponse, + parse_obj_as( + type_=DeleteEvaluationRuleResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawEvaluationRulesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + name: str, + evaluator: EvaluationRuleEvaluatorReference, + target: EvaluationRuleTarget, + enabled: bool, + mapping: typing.Sequence[EvaluationRuleMapping], + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EvaluationRule]: + """ + Create an evaluation rule. + + An evaluation rule defines **what** incoming data should be evaluated and **how prompt variables should be populated** from that data. + + Use this resource after choosing an evaluator from the evaluator endpoints. + + Key rules: + - `name` must be unique within the project for public evaluation rules + - `target` must be `observation` or `experiment` + - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints + - Langfuse resolves that family to its latest version before saving the evaluation rule + - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` + - every evaluator prompt variable must be mapped exactly once + - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run + - at most 50 evaluation rules can be effectively active in one project at the same time + + If an evaluation rule with the same `name` already exists in the project, the API returns `409`. + In that case, update the existing resource with `PATCH /api/public/unstable/evaluation-rules/{evaluationRuleId}` instead of creating a second one. + + If enabling this resource would exceed the 50-active limit, the API also returns `409`. + In that case, disable or pause another active evaluation rule before enabling a new one. + + Current scope: + - evaluation rules are live-ingestion rules only + - they do not trigger historical backfills + + Recovery guidance: + - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` + - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response + - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable + - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_json_path`: remove or correct the `jsonPath` + - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. + + Parameters + ---------- + name : str + Human-readable deployment name. + + evaluator : EvaluationRuleEvaluatorReference + Evaluator family to use. + + Use `name` and `scope` from the evaluator endpoints. + Langfuse resolves that family to its latest version before saving the rule. + + target : EvaluationRuleTarget + Target object type to evaluate. + + enabled : bool + Whether the deployment should be active immediately after creation. + + mapping : typing.Sequence[EvaluationRuleMapping] + Required variable mappings. + + Every evaluator variable must appear exactly once. + Build this list from the evaluator `variables` array returned by the evaluator endpoints. + + sampling : typing.Optional[float] + Optional sampling fraction. Defaults to `1`. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EvaluationRule] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluation-rules", + method="POST", + json={ + "name": name, + "evaluator": convert_and_respect_annotation_metadata( + object_=evaluator, + annotation=EvaluationRuleEvaluatorReference, + direction="write", + ), + "target": target, + "enabled": enabled, + "sampling": sampling, + "filter": convert_and_respect_annotation_metadata( + object_=filter, + annotation=typing.Sequence[EvaluationRuleFilter], + direction="write", + ), + "mapping": convert_and_respect_annotation_metadata( + object_=mapping, + annotation=typing.Sequence[EvaluationRuleMapping], + direction="write", + ), + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRule, + parse_obj_as( + type_=EvaluationRule, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableContentError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EvaluationRules]: + """ + List evaluation rules in the authenticated project. + + Each item describes one live evaluation rule and its effective runtime status. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EvaluationRules] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluation-rules", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRules, + parse_obj_as( + type_=EvaluationRules, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EvaluationRule]: + """ + Get one evaluation rule by its identifier. + + Use this endpoint to inspect the current evaluator, target, mapping, filters, and effective runtime status. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier returned by the evaluation rule endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EvaluationRule] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluation-rules/{jsonable_encoder(evaluation_rule_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRule, + parse_obj_as( + type_=EvaluationRule, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def update( + self, + evaluation_rule_id: str, + *, + name: typing.Optional[str] = OMIT, + evaluator: typing.Optional[EvaluationRuleEvaluatorReference] = OMIT, + target: typing.Optional[EvaluationRuleTarget] = OMIT, + enabled: typing.Optional[bool] = OMIT, + sampling: typing.Optional[float] = OMIT, + filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + mapping: typing.Optional[typing.Sequence[EvaluationRuleMapping]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[EvaluationRule]: + """ + Update an evaluation rule. + + Typical uses: + - enable or disable live execution + - switch to another evaluator + - adjust sampling + - change filters + - update variable mappings + + Important behavior: + - provide only the fields you want to change + - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving + - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration + - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run + - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` + + Recovery guidance: + - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + name : typing.Optional[str] + Updated deployment name. + + evaluator : typing.Optional[EvaluationRuleEvaluatorReference] + Updated evaluator family. + + Langfuse resolves the provided evaluator family to its latest version before saving the rule. + + target : typing.Optional[EvaluationRuleTarget] + Updated target object type. + + enabled : typing.Optional[bool] + Updated desired enabled state. + + sampling : typing.Optional[float] + Updated sampling fraction. + + filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] + Updated filter list. + + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] + Updated variable mappings. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[EvaluationRule] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluation-rules/{jsonable_encoder(evaluation_rule_id)}", + method="PATCH", + json={ + "name": name, + "evaluator": convert_and_respect_annotation_metadata( + object_=evaluator, + annotation=EvaluationRuleEvaluatorReference, + direction="write", + ), + "target": target, + "enabled": enabled, + "sampling": sampling, + "filter": convert_and_respect_annotation_metadata( + object_=filter, + annotation=typing.Sequence[EvaluationRuleFilter], + direction="write", + ), + "mapping": convert_and_respect_annotation_metadata( + object_=mapping, + annotation=typing.Sequence[EvaluationRuleMapping], + direction="write", + ), + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + EvaluationRule, + parse_obj_as( + type_=EvaluationRule, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableContentError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def delete( + self, + evaluation_rule_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DeleteEvaluationRuleResponse]: + """ + Delete an evaluation rule. + + This removes the live-ingestion rule only. It does not delete the referenced evaluator. + + Parameters + ---------- + evaluation_rule_id : str + Evaluation rule identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteEvaluationRuleResponse] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluation-rules/{jsonable_encoder(evaluation_rule_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteEvaluationRuleResponse, + parse_obj_as( + type_=DeleteEvaluationRuleResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/__init__.py b/langfuse/api/unstable/evaluation_rules/types/__init__.py new file mode 100644 index 000000000..2854b1237 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/__init__.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_evaluation_rule_request import CreateEvaluationRuleRequest + from .delete_evaluation_rule_response import DeleteEvaluationRuleResponse + from .evaluation_rule import EvaluationRule + from .evaluation_rule_evaluator import EvaluationRuleEvaluator + from .evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference + from .evaluation_rules import EvaluationRules + from .update_evaluation_rule_request import UpdateEvaluationRuleRequest +_dynamic_imports: typing.Dict[str, str] = { + "CreateEvaluationRuleRequest": ".create_evaluation_rule_request", + "DeleteEvaluationRuleResponse": ".delete_evaluation_rule_response", + "EvaluationRule": ".evaluation_rule", + "EvaluationRuleEvaluator": ".evaluation_rule_evaluator", + "EvaluationRuleEvaluatorReference": ".evaluation_rule_evaluator_reference", + "EvaluationRules": ".evaluation_rules", + "UpdateEvaluationRuleRequest": ".update_evaluation_rule_request", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateEvaluationRuleRequest", + "DeleteEvaluationRuleResponse", + "EvaluationRule", + "EvaluationRuleEvaluator", + "EvaluationRuleEvaluatorReference", + "EvaluationRules", + "UpdateEvaluationRuleRequest", +] diff --git a/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py b/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py new file mode 100644 index 000000000..9a90b227a --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py @@ -0,0 +1,75 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ...commons.types.evaluation_rule_mapping import EvaluationRuleMapping +from ...commons.types.evaluation_rule_target import EvaluationRuleTarget +from .evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference + + +class CreateEvaluationRuleRequest(UniversalBaseModel): + """ + Request body for creating an evaluation rule. + + Checklist for agents and SDK clients: + - reference an existing evaluator family by `evaluator.name` and `evaluator.scope` + - choose `target=observation` or `target=experiment` + - if `target=experiment` and you want a dataset filter, call `GET /api/public/v2/datasets` first and use dataset `id` values in `filter[].value` + - fetch or inspect the evaluator first, then provide a complete variable mapping for every evaluator variable listed in `variables` + - optionally narrow execution with `filter` + - set `enabled=true` only when you want live execution immediately + """ + + name: str = pydantic.Field() + """ + Human-readable deployment name. + """ + + evaluator: EvaluationRuleEvaluatorReference = pydantic.Field() + """ + Evaluator family to use. + + Use `name` and `scope` from the evaluator endpoints. + Langfuse resolves that family to its latest version before saving the rule. + """ + + target: EvaluationRuleTarget = pydantic.Field() + """ + Target object type to evaluate. + """ + + enabled: bool = pydantic.Field() + """ + Whether the deployment should be active immediately after creation. + """ + + sampling: typing.Optional[float] = pydantic.Field(default=None) + """ + Optional sampling fraction. Defaults to `1`. + """ + + filter: typing.Optional[typing.List[EvaluationRuleFilter]] = pydantic.Field( + default=None + ) + """ + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + """ + + mapping: typing.List[EvaluationRuleMapping] = pydantic.Field() + """ + Required variable mappings. + + Every evaluator variable must appear exactly once. + Build this list from the evaluator `variables` array returned by the evaluator endpoints. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/delete_evaluation_rule_response.py b/langfuse/api/unstable/evaluation_rules/types/delete_evaluation_rule_response.py new file mode 100644 index 000000000..42423c3dc --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/delete_evaluation_rule_response.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class DeleteEvaluationRuleResponse(UniversalBaseModel): + """ + Confirmation response returned after successful deletion. + """ + + message: str = pydantic.Field() + """ + Always `Evaluation rule successfully deleted`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py new file mode 100644 index 000000000..d8baee407 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py @@ -0,0 +1,172 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ...commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ...commons.types.evaluation_rule_mapping import EvaluationRuleMapping +from ...commons.types.evaluation_rule_status import EvaluationRuleStatus +from ...commons.types.evaluation_rule_target import EvaluationRuleTarget +from .evaluation_rule_evaluator import EvaluationRuleEvaluator + + +class EvaluationRule(UniversalBaseModel): + """ + Live evaluation rule for incoming data. + + An evaluation rule answers: + - which evaluator should be used + - which target objects should trigger scoring + - how often scoring should run + - which target fields should populate each evaluator variable + - whether the deployment is active, inactive, or paused + + Important status semantics: + - `enabled` is the desired on/off setting from the client + - `status` is the effective runtime state after Langfuse applies validation and blocking rules + - `enabled=true` with `status=paused` means the rule should run, but Langfuse has paused it until the underlying problem is fixed + + Examples + -------- + import datetime + + from langfuse.unstable.commons import ( + EvaluationRuleFilter_StringOptions, + EvaluationRuleMapping, + EvaluationRuleMappingSource, + EvaluationRuleOptionsFilterOperator, + EvaluationRuleStatus, + EvaluationRuleTarget, + EvaluatorScope, + ) + from langfuse.unstable.evaluation_rules import ( + EvaluationRule, + EvaluationRuleEvaluator, + ) + + EvaluationRule( + id="erule_123", + name="answer-correctness-live", + evaluator=EvaluationRuleEvaluator( + id="evaltmpl_123", + name="answer-correctness", + scope=EvaluatorScope.PROJECT, + ), + target=EvaluationRuleTarget.OBSERVATION, + enabled=True, + status=EvaluationRuleStatus.ACTIVE, + sampling=1.0, + filter=[ + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + ], + mapping=[ + EvaluationRuleMapping( + variable="input", + source=EvaluationRuleMappingSource.INPUT, + ), + EvaluationRuleMapping( + variable="output", + source=EvaluationRuleMappingSource.OUTPUT, + ), + ], + created_at=datetime.datetime.fromisoformat( + "2026-03-30 09:20:00+00:00", + ), + updated_at=datetime.datetime.fromisoformat( + "2026-03-30 09:20:00+00:00", + ), + ) + """ + + id: str = pydantic.Field() + """ + Stable evaluation rule identifier. + """ + + name: str = pydantic.Field() + """ + Human-readable deployment name. This is independent from the evaluator name. + """ + + evaluator: EvaluationRuleEvaluator = pydantic.Field() + """ + Evaluator currently used by this rule. + + `name` and `scope` identify the evaluator family conceptually. + `id` is the currently active evaluator version in that family. + If you create a newer project version with the same evaluator name later, existing evaluation rules are moved to it automatically. + """ + + target: EvaluationRuleTarget = pydantic.Field() + """ + Target object type that should trigger scoring. + """ + + enabled: bool = pydantic.Field() + """ + Desired enabled state configured by the client. + """ + + status: EvaluationRuleStatus = pydantic.Field() + """ + Effective runtime status after Langfuse applies validation and blocking rules. + """ + + paused_reason: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="pausedReason") + ] = pydantic.Field(default=None) + """ + Machine-readable reason when `status=paused`, otherwise `null`. + """ + + paused_message: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="pausedMessage") + ] = pydantic.Field(default=None) + """ + Human-readable explanation when `status=paused`, otherwise `null`. + """ + + sampling: float = pydantic.Field() + """ + Fraction of matching target objects that should be evaluated. + + Must be greater than `0` and less than or equal to `1`. + - `1` means evaluate every matching target. + - `0.25` means evaluate approximately 25% of matching targets. + """ + + filter: typing.List[EvaluationRuleFilter] = pydantic.Field() + """ + List of filter conditions used to decide whether a target should be evaluated. + """ + + mapping: typing.List[EvaluationRuleMapping] = pydantic.Field() + """ + Variable mappings used to populate the evaluator prompt from the live target object. + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] = pydantic.Field() + """ + Timestamp when the evaluation rule was created. + """ + + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] = pydantic.Field() + """ + Timestamp when the evaluation rule was last updated. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py new file mode 100644 index 000000000..9d1be79de --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluator_scope import EvaluatorScope + + +class EvaluationRuleEvaluator(UniversalBaseModel): + """ + Resolved evaluator currently used by the evaluation rule. + + `id` is the exact active evaluator version. + `name` and `scope` identify the evaluator family conceptually. + """ + + id: str = pydantic.Field() + """ + Identifier of the exact evaluator version currently used by the rule. + """ + + name: str = pydantic.Field() + """ + Evaluator family name. + """ + + scope: EvaluatorScope = pydantic.Field() + """ + Whether the evaluator family is project-owned or Langfuse-managed. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py new file mode 100644 index 000000000..25253182f --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluator_scope import EvaluatorScope + + +class EvaluationRuleEvaluatorReference(UniversalBaseModel): + """ + Evaluator family reference used when creating or updating an evaluation rule. + + `name` and `scope` are enough to identify the evaluator family in the authenticated project context. + """ + + name: str = pydantic.Field() + """ + Evaluator family name. + """ + + scope: EvaluatorScope = pydantic.Field() + """ + Whether the evaluator family is project-owned or Langfuse-managed. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rules.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rules.py new file mode 100644 index 000000000..cd1f74c6d --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rules.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse +from .evaluation_rule import EvaluationRule + + +class EvaluationRules(UniversalBaseModel): + """ + Paginated list of evaluation rules. + """ + + data: typing.List[EvaluationRule] = pydantic.Field() + """ + Evaluation rules in the current page. + """ + + meta: MetaResponse = pydantic.Field() + """ + Standard pagination metadata. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py b/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py new file mode 100644 index 000000000..51e2d9288 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ...commons.types.evaluation_rule_mapping import EvaluationRuleMapping +from ...commons.types.evaluation_rule_target import EvaluationRuleTarget +from .evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference + + +class UpdateEvaluationRuleRequest(UniversalBaseModel): + """ + Partial update body for an evaluation rule. + + Provide only the fields you want to change. + An empty body is rejected. + + Practical guidance: + - If you only want to rename the rule or change sampling, send just those fields. + - If you change `evaluator`, send a fresh `mapping` unless you are certain the existing mapping still matches the evaluator variables. + - If you change `target`, usually send both `filter` and `mapping` in the same request. + - If you change an experiment `datasetId` filter, call `GET /api/public/v2/datasets` and use dataset `id` values from that response. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Updated deployment name. + """ + + evaluator: typing.Optional[EvaluationRuleEvaluatorReference] = pydantic.Field( + default=None + ) + """ + Updated evaluator family. + + Langfuse resolves the provided evaluator family to its latest version before saving the rule. + """ + + target: typing.Optional[EvaluationRuleTarget] = pydantic.Field(default=None) + """ + Updated target object type. + """ + + enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Updated desired enabled state. + """ + + sampling: typing.Optional[float] = pydantic.Field(default=None) + """ + Updated sampling fraction. + """ + + filter: typing.Optional[typing.List[EvaluationRuleFilter]] = pydantic.Field( + default=None + ) + """ + Updated filter list. + + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + """ + + mapping: typing.Optional[typing.List[EvaluationRuleMapping]] = pydantic.Field( + default=None + ) + """ + Updated variable mappings. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/__init__.py b/langfuse/api/unstable/evaluators/__init__.py new file mode 100644 index 000000000..942109740 --- /dev/null +++ b/langfuse/api/unstable/evaluators/__init__.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateEvaluatorRequest, Evaluator, Evaluators +_dynamic_imports: typing.Dict[str, str] = { + "CreateEvaluatorRequest": ".types", + "Evaluator": ".types", + "Evaluators": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateEvaluatorRequest", "Evaluator", "Evaluators"] diff --git a/langfuse/api/unstable/evaluators/client.py b/langfuse/api/unstable/evaluators/client.py new file mode 100644 index 000000000..b7f25532a --- /dev/null +++ b/langfuse/api/unstable/evaluators/client.py @@ -0,0 +1,458 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ..commons.types.evaluator_model_config import EvaluatorModelConfig +from ..commons.types.evaluator_output_definition import EvaluatorOutputDefinition +from .raw_client import AsyncRawEvaluatorsClient, RawEvaluatorsClient +from .types.evaluator import Evaluator +from .types.evaluators import Evaluators + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class EvaluatorsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawEvaluatorsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawEvaluatorsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawEvaluatorsClient + """ + return self._raw_client + + def create( + self, + *, + name: str, + prompt: str, + output_definition: EvaluatorOutputDefinition, + model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Evaluator: + """ + Create an evaluator in the authenticated project. + + Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + + Naming behavior: + - If this is a new evaluator name in your project, Langfuse creates version `1`. + - If the name already exists in your project, Langfuse creates the next version and returns it. + - When a new project version is created, existing evaluation rules in that project automatically move to the newest version for that evaluator name. + + Recommended workflow: + 1. Create the evaluator. + 2. Read the returned `variables` array. + 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. + 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + + Recovery guidance: + - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. + - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. + - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + + Unstable API note: + - This surface may evolve while the underlying evaluation data model is being redesigned. + + Parameters + ---------- + name : str + Evaluator name within the authenticated project. + + prompt : str + Prompt template used by the evaluator. + + output_definition : EvaluatorOutputDefinition + Structured output schema the evaluator must return. + + Always send `dataType`. + Do not send `version`; it is an internal storage detail and not part of the public request contract. + + model_config : typing.Optional[EvaluatorModelConfig] + Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Evaluator + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.unstable.commons import ( + EvaluatorModelConfig, + EvaluatorOutputDataType, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + ) + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluators.create( + name="answer-correctness", + prompt="You are grading an answer.\n\nInput:\n{{input}}\n\nOutput:\n{{output}}\n\nReturn a score between 0 and 1.\n", + output_definition=EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the score was assigned.", + ), + score=EvaluatorOutputFieldDefinition( + description="Correctness score between 0 and 1.", + ), + ), + model_config=EvaluatorModelConfig( + provider="openai", + model="gpt-4.1-mini", + ), + ) + """ + _response = self._raw_client.create( + name=name, + prompt=prompt, + output_definition=output_definition, + model_config=model_config, + request_options=request_options, + ) + return _response.data + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> Evaluators: + """ + List the evaluators available to the authenticated project. + + Important behavior: + - This endpoint returns the latest version of each available evaluator. + - Results can include evaluators from your project and Langfuse-managed evaluators. + - If the same evaluator name exists in both places, both are returned as separate items with different `scope` values. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Evaluators + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluators.list() + """ + _response = self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get( + self, + evaluator_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> Evaluator: + """ + Get one evaluator by `id`. + + Use this endpoint when you want the prompt, output definition, model configuration, and derived variables for the evaluator you plan to use in an evaluation rule. + + Parameters + ---------- + evaluator_id : str + Evaluator identifier returned by the evaluator endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Evaluator + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.evaluators.get( + evaluator_id="evaluatorId", + ) + """ + _response = self._raw_client.get(evaluator_id, request_options=request_options) + return _response.data + + +class AsyncEvaluatorsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawEvaluatorsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawEvaluatorsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawEvaluatorsClient + """ + return self._raw_client + + async def create( + self, + *, + name: str, + prompt: str, + output_definition: EvaluatorOutputDefinition, + model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Evaluator: + """ + Create an evaluator in the authenticated project. + + Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + + Naming behavior: + - If this is a new evaluator name in your project, Langfuse creates version `1`. + - If the name already exists in your project, Langfuse creates the next version and returns it. + - When a new project version is created, existing evaluation rules in that project automatically move to the newest version for that evaluator name. + + Recommended workflow: + 1. Create the evaluator. + 2. Read the returned `variables` array. + 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. + 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + + Recovery guidance: + - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. + - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. + - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + + Unstable API note: + - This surface may evolve while the underlying evaluation data model is being redesigned. + + Parameters + ---------- + name : str + Evaluator name within the authenticated project. + + prompt : str + Prompt template used by the evaluator. + + output_definition : EvaluatorOutputDefinition + Structured output schema the evaluator must return. + + Always send `dataType`. + Do not send `version`; it is an internal storage detail and not part of the public request contract. + + model_config : typing.Optional[EvaluatorModelConfig] + Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Evaluator + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.unstable.commons import ( + EvaluatorModelConfig, + EvaluatorOutputDataType, + EvaluatorOutputDefinition_Numeric, + EvaluatorOutputFieldDefinition, + ) + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluators.create( + name="answer-correctness", + prompt="You are grading an answer.\n\nInput:\n{{input}}\n\nOutput:\n{{output}}\n\nReturn a score between 0 and 1.\n", + output_definition=EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the score was assigned.", + ), + score=EvaluatorOutputFieldDefinition( + description="Correctness score between 0 and 1.", + ), + ), + model_config=EvaluatorModelConfig( + provider="openai", + model="gpt-4.1-mini", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + prompt=prompt, + output_definition=output_definition, + model_config=model_config, + request_options=request_options, + ) + return _response.data + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> Evaluators: + """ + List the evaluators available to the authenticated project. + + Important behavior: + - This endpoint returns the latest version of each available evaluator. + - Results can include evaluators from your project and Langfuse-managed evaluators. + - If the same evaluator name exists in both places, both are returned as separate items with different `scope` values. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Evaluators + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluators.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get( + self, + evaluator_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> Evaluator: + """ + Get one evaluator by `id`. + + Use this endpoint when you want the prompt, output definition, model configuration, and derived variables for the evaluator you plan to use in an evaluation rule. + + Parameters + ---------- + evaluator_id : str + Evaluator identifier returned by the evaluator endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Evaluator + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.evaluators.get( + evaluator_id="evaluatorId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + evaluator_id, request_options=request_options + ) + return _response.data diff --git a/langfuse/api/unstable/evaluators/raw_client.py b/langfuse/api/unstable/evaluators/raw_client.py new file mode 100644 index 000000000..f599e3298 --- /dev/null +++ b/langfuse/api/unstable/evaluators/raw_client.py @@ -0,0 +1,1278 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...commons.errors.access_denied_error import ( + AccessDeniedError as commons_errors_access_denied_error_AccessDeniedError, +) +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import ( + MethodNotAllowedError as commons_errors_method_not_allowed_error_MethodNotAllowedError, +) +from ...commons.errors.not_found_error import ( + NotFoundError as commons_errors_not_found_error_NotFoundError, +) +from ...commons.errors.unauthorized_error import ( + UnauthorizedError as commons_errors_unauthorized_error_UnauthorizedError, +) +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import jsonable_encoder +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata +from ..commons.types.evaluator_model_config import EvaluatorModelConfig +from ..commons.types.evaluator_output_definition import EvaluatorOutputDefinition +from ..errors.errors.access_denied_error import ( + AccessDeniedError as unstable_errors_errors_access_denied_error_AccessDeniedError, +) +from ..errors.errors.bad_request_error import BadRequestError +from ..errors.errors.conflict_error import ConflictError +from ..errors.errors.internal_server_error import InternalServerError +from ..errors.errors.method_not_allowed_error import ( + MethodNotAllowedError as unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError, +) +from ..errors.errors.not_found_error import ( + NotFoundError as unstable_errors_errors_not_found_error_NotFoundError, +) +from ..errors.errors.too_many_requests_error import TooManyRequestsError +from ..errors.errors.unauthorized_error import ( + UnauthorizedError as unstable_errors_errors_unauthorized_error_UnauthorizedError, +) +from ..errors.errors.unprocessable_content_error import UnprocessableContentError +from ..errors.types.public_api_error import PublicApiError +from .types.evaluator import Evaluator +from .types.evaluators import Evaluators + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawEvaluatorsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + name: str, + prompt: str, + output_definition: EvaluatorOutputDefinition, + model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Evaluator]: + """ + Create an evaluator in the authenticated project. + + Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + + Naming behavior: + - If this is a new evaluator name in your project, Langfuse creates version `1`. + - If the name already exists in your project, Langfuse creates the next version and returns it. + - When a new project version is created, existing evaluation rules in that project automatically move to the newest version for that evaluator name. + + Recommended workflow: + 1. Create the evaluator. + 2. Read the returned `variables` array. + 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. + 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + + Recovery guidance: + - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. + - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. + - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + + Unstable API note: + - This surface may evolve while the underlying evaluation data model is being redesigned. + + Parameters + ---------- + name : str + Evaluator name within the authenticated project. + + prompt : str + Prompt template used by the evaluator. + + output_definition : EvaluatorOutputDefinition + Structured output schema the evaluator must return. + + Always send `dataType`. + Do not send `version`; it is an internal storage detail and not part of the public request contract. + + model_config : typing.Optional[EvaluatorModelConfig] + Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Evaluator] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluators", + method="POST", + json={ + "name": name, + "prompt": prompt, + "outputDefinition": convert_and_respect_annotation_metadata( + object_=output_definition, + annotation=EvaluatorOutputDefinition, + direction="write", + ), + "modelConfig": convert_and_respect_annotation_metadata( + object_=model_config, + annotation=typing.Optional[EvaluatorModelConfig], + direction="write", + ), + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Evaluator, + parse_obj_as( + type_=Evaluator, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableContentError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Evaluators]: + """ + List the evaluators available to the authenticated project. + + Important behavior: + - This endpoint returns the latest version of each available evaluator. + - Results can include evaluators from your project and Langfuse-managed evaluators. + - If the same evaluator name exists in both places, both are returned as separate items with different `scope` values. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Evaluators] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluators", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Evaluators, + parse_obj_as( + type_=Evaluators, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + def get( + self, + evaluator_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Evaluator]: + """ + Get one evaluator by `id`. + + Use this endpoint when you want the prompt, output definition, model configuration, and derived variables for the evaluator you plan to use in an evaluation rule. + + Parameters + ---------- + evaluator_id : str + Evaluator identifier returned by the evaluator endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Evaluator] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluators/{jsonable_encoder(evaluator_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Evaluator, + parse_obj_as( + type_=Evaluator, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawEvaluatorsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + name: str, + prompt: str, + output_definition: EvaluatorOutputDefinition, + model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Evaluator]: + """ + Create an evaluator in the authenticated project. + + Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + + Naming behavior: + - If this is a new evaluator name in your project, Langfuse creates version `1`. + - If the name already exists in your project, Langfuse creates the next version and returns it. + - When a new project version is created, existing evaluation rules in that project automatically move to the newest version for that evaluator name. + + Recommended workflow: + 1. Create the evaluator. + 2. Read the returned `variables` array. + 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. + 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + + Recovery guidance: + - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. + - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. + - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + + Unstable API note: + - This surface may evolve while the underlying evaluation data model is being redesigned. + + Parameters + ---------- + name : str + Evaluator name within the authenticated project. + + prompt : str + Prompt template used by the evaluator. + + output_definition : EvaluatorOutputDefinition + Structured output schema the evaluator must return. + + Always send `dataType`. + Do not send `version`; it is an internal storage detail and not part of the public request contract. + + model_config : typing.Optional[EvaluatorModelConfig] + Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Evaluator] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluators", + method="POST", + json={ + "name": name, + "prompt": prompt, + "outputDefinition": convert_and_respect_annotation_metadata( + object_=output_definition, + annotation=EvaluatorOutputDefinition, + direction="write", + ), + "modelConfig": convert_and_respect_annotation_metadata( + object_=model_config, + annotation=typing.Optional[EvaluatorModelConfig], + direction="write", + ), + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Evaluator, + parse_obj_as( + type_=Evaluator, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 409: + raise ConflictError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableContentError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def list( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Evaluators]: + """ + List the evaluators available to the authenticated project. + + Important behavior: + - This endpoint returns the latest version of each available evaluator. + - Results can include evaluators from your project and Langfuse-managed evaluators. + - If the same evaluator name exists in both places, both are returned as separate items with different `scope` values. + + Parameters + ---------- + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Evaluators] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/evaluators", + method="GET", + params={ + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Evaluators, + parse_obj_as( + type_=Evaluators, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + async def get( + self, + evaluator_id: str, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Evaluator]: + """ + Get one evaluator by `id`. + + Use this endpoint when you want the prompt, output definition, model configuration, and derived variables for the evaluator you plan to use in an evaluation rule. + + Parameters + ---------- + evaluator_id : str + Evaluator identifier returned by the evaluator endpoints. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Evaluator] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/evaluators/{jsonable_encoder(evaluator_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Evaluator, + parse_obj_as( + type_=Evaluator, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise commons_errors_not_found_error_NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/unstable/evaluators/types/__init__.py b/langfuse/api/unstable/evaluators/types/__init__.py new file mode 100644 index 000000000..6e7a13233 --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/__init__.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_evaluator_request import CreateEvaluatorRequest + from .evaluator import Evaluator + from .evaluators import Evaluators +_dynamic_imports: typing.Dict[str, str] = { + "CreateEvaluatorRequest": ".create_evaluator_request", + "Evaluator": ".evaluator", + "Evaluators": ".evaluators", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateEvaluatorRequest", "Evaluator", "Evaluators"] diff --git a/langfuse/api/unstable/evaluators/types/create_evaluator_request.py b/langfuse/api/unstable/evaluators/types/create_evaluator_request.py new file mode 100644 index 000000000..7616d99ee --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/create_evaluator_request.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ...commons.types.evaluator_model_config import EvaluatorModelConfig +from ...commons.types.evaluator_output_definition import EvaluatorOutputDefinition + + +class CreateEvaluatorRequest(UniversalBaseModel): + """ + Request body for creating an evaluator. + + If the same `name` already exists in your project, Langfuse creates the next version and returns it. + Existing evaluation rules in the same project are then moved to that new latest version automatically. + """ + + name: str = pydantic.Field() + """ + Evaluator name within the authenticated project. + """ + + prompt: str = pydantic.Field() + """ + Prompt template used by the evaluator. + """ + + output_definition: typing_extensions.Annotated[ + EvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") + ] = pydantic.Field() + """ + Structured output schema the evaluator must return. + + Always send `dataType`. + Do not send `version`; it is an internal storage detail and not part of the public request contract. + """ + + model_config_: typing_extensions.Annotated[ + typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") + ] = pydantic.Field(default=None) + """ + Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/types/evaluator.py b/langfuse/api/unstable/evaluators/types/evaluator.py new file mode 100644 index 000000000..8023839fc --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/evaluator.py @@ -0,0 +1,118 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ...commons.types.evaluator_model_config import EvaluatorModelConfig +from ...commons.types.evaluator_scope import EvaluatorScope +from ...commons.types.evaluator_type import EvaluatorType +from ...commons.types.public_evaluator_output_definition import ( + PublicEvaluatorOutputDefinition, +) + + +class Evaluator(UniversalBaseModel): + """ + One evaluator that can be used for scoring. + + An evaluator describes **how** to score data: + - prompt + - extracted prompt variables + - output schema + - optional explicit model configuration + + It does not define **which** live objects are evaluated. That is the job of `evaluation-rules`. + + For agent clients, the most important fields are: + - `variables`: use these exact names when building the evaluation-rule `mapping` array + - `outputDefinition`: tells you the expected score type and the evaluator's response instructions + - `modelConfig`: tells you whether the evaluator uses the project default model (`null`) or an explicit provider/model + + Versioning behavior: + - `GET /evaluators` returns the latest version of each available evaluator. + - `GET /evaluators/{id}` can return an older version. + - Evaluation rules always run against the latest version for the selected evaluator name within the same source (`project` or `managed`). + """ + + id: str = pydantic.Field() + """ + Identifier of this evaluator. + """ + + name: str = pydantic.Field() + """ + Evaluator name. + """ + + version: int = pydantic.Field() + """ + Version number of this evaluator. + """ + + scope: EvaluatorScope = pydantic.Field() + """ + Where this evaluator comes from: your project or Langfuse-managed defaults. + """ + + type: EvaluatorType = pydantic.Field() + """ + Evaluator engine type. Currently always `llm_as_judge`. + """ + + prompt: str = pydantic.Field() + """ + Prompt template used during evaluation. + """ + + variables: typing.List[str] = pydantic.Field() + """ + Variables extracted from the evaluator prompt. + + Every variable in this list must be mapped exactly once when creating an evaluation rule. + """ + + output_definition: typing_extensions.Annotated[ + PublicEvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") + ] = pydantic.Field() + """ + Structured output schema returned by this evaluator. + + Responses always include `dataType` and omit the internal output-definition `version`. + Use `dataType` to decide how future scores should be interpreted. + """ + + model_config_: typing_extensions.Annotated[ + typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") + ] = pydantic.Field(default=None) + """ + Explicit model configuration, or `null` when the project default evaluation model is used. + """ + + evaluation_rule_count: typing_extensions.Annotated[ + int, FieldMetadata(alias="evaluationRuleCount") + ] = pydantic.Field() + """ + Number of evaluation rules in the project that currently use this evaluator version. + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] = pydantic.Field() + """ + Timestamp when this evaluator was created. + """ + + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] = pydantic.Field() + """ + Timestamp when this evaluator was last updated. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/types/evaluators.py b/langfuse/api/unstable/evaluators/types/evaluators.py new file mode 100644 index 000000000..51247a66e --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/evaluators.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse +from .evaluator import Evaluator + + +class Evaluators(UniversalBaseModel): + data: typing.List[Evaluator] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/raw_client.py b/langfuse/api/unstable/raw_client.py new file mode 100644 index 000000000..5201a5119 --- /dev/null +++ b/langfuse/api/unstable/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawUnstableClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawUnstableClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper From 76e66b29b8966429d102d48122ea8afeb64740bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 09:34:26 +0200 Subject: [PATCH 286/296] chore(deps): bump github/codeql-action from 4.35.5 to 4.36.0 in the github-actions group (#1678) chore(deps): bump github/codeql-action in the github-actions group Bumps the github-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.35.5 to 4.36.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/9e0d7b8d25671d64c341c19c0152d693099fb5ba...7211b7c8077ea37d8641b6271f6a365a22a5fbfa) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fa9b6ddb6..b10796c71 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,7 +61,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -89,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: category: "/language:${{matrix.language}}" From 50e8f916ba118c33585d01362c19e00c022e0265 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Fri, 29 May 2026 18:06:17 +0000 Subject: [PATCH 287/296] chore: release v4.7.1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e37912fbb..0c66a4aed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.7.0" +version = "4.7.1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 83581a848..2da4263fb 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-05-20T18:50:51.815107415Z" +exclude-newer = "2026-05-22T18:06:12.465516488Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.7.0" +version = "4.7.1" source = { editable = "." } dependencies = [ { name = "backoff" }, From 8631341f33e9757a2eb6481d56e1425f96a025d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannik=20Maierh=C3=B6fer?= <48529566+jannikmaierhoefer@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:02:37 +0200 Subject: [PATCH 288/296] docs: update README banner --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6eb845a75..de79a88fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Langfuse GitHub Banner](https://langfuse.com/langfuse_logo_white.png) +hero-b # Langfuse Python SDK From 003e045b680faa639d593016a78f29924b268244 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 2 Jun 2026 16:25:57 +0200 Subject: [PATCH 289/296] feat(api): update API spec from langfuse/langfuse 9ce4aa4 (#1685) Co-authored-by: langfuse-bot --- langfuse/api/llm_connections/client.py | 4 ++-- langfuse/api/llm_connections/raw_client.py | 4 ++-- langfuse/api/llm_connections/types/llm_connection.py | 2 +- .../types/upsert_llm_connection_request.py | 2 +- langfuse/api/observations/client.py | 12 ++++++------ langfuse/api/observations/raw_client.py | 12 ++++++------ 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/langfuse/api/llm_connections/client.py b/langfuse/api/llm_connections/client.py index 62c4293ff..3d4e60d00 100644 --- a/langfuse/api/llm_connections/client.py +++ b/langfuse/api/llm_connections/client.py @@ -113,7 +113,7 @@ def upsert( Extra headers to send with requests config : typing.Optional[typing.Dict[str, typing.Any]] - Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must be `{"useResponsesApi": }` to control whether Langfuse routes calls through OpenAI's Responses API. - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -298,7 +298,7 @@ async def upsert( Extra headers to send with requests config : typing.Optional[typing.Dict[str, typing.Any]] - Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must be `{"useResponsesApi": }` to control whether Langfuse routes calls through OpenAI's Responses API. - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/llm_connections/raw_client.py b/langfuse/api/llm_connections/raw_client.py index 30f7beebb..9a6388658 100644 --- a/langfuse/api/llm_connections/raw_client.py +++ b/langfuse/api/llm_connections/raw_client.py @@ -179,7 +179,7 @@ def upsert( Extra headers to send with requests config : typing.Optional[typing.Dict[str, typing.Any]] - Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must be `{"useResponsesApi": }` to control whether Langfuse routes calls through OpenAI's Responses API. - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -539,7 +539,7 @@ async def upsert( Extra headers to send with requests config : typing.Optional[typing.Dict[str, typing.Any]] - Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must be `{"useResponsesApi": }` to control whether Langfuse routes calls through OpenAI's Responses API. - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/llm_connections/types/llm_connection.py b/langfuse/api/llm_connections/types/llm_connection.py index f74ff98c2..470fd3d76 100644 --- a/langfuse/api/llm_connections/types/llm_connection.py +++ b/langfuse/api/llm_connections/types/llm_connection.py @@ -62,7 +62,7 @@ class LlmConnection(UniversalBaseModel): config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) """ - Adapter-specific configuration. Required for Bedrock (`{"region":"us-east-1"}`), optional for VertexAI (`{"location":"us-central1"}`), not used by other adapters. + Adapter-specific configuration. Required for Bedrock (`{"region":"us-east-1"}`), optional for OpenAI (`{"useResponsesApi":true}`), optional for VertexAI (`{"location":"us-central1"}`), not used by other adapters. """ created_at: typing_extensions.Annotated[ diff --git a/langfuse/api/llm_connections/types/upsert_llm_connection_request.py b/langfuse/api/llm_connections/types/upsert_llm_connection_request.py index 712362fa1..4d99f183a 100644 --- a/langfuse/api/llm_connections/types/upsert_llm_connection_request.py +++ b/langfuse/api/llm_connections/types/upsert_llm_connection_request.py @@ -61,7 +61,7 @@ class UpsertLlmConnectionRequest(UniversalBaseModel): config: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) """ - Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. + Adapter-specific configuration. Validation rules: - **Bedrock**: Required. Must be `{"region": ""}` (e.g., `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must be `{"useResponsesApi": }` to control whether Langfuse routes calls through OpenAI's Responses API. - **VertexAI**: Optional. If provided, must be `{"location": ""}` (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not supported. Omit this field or set to null. """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( diff --git a/langfuse/api/observations/client.py b/langfuse/api/observations/client.py index ff7069ede..995dba08b 100644 --- a/langfuse/api/observations/client.py +++ b/langfuse/api/observations/client.py @@ -193,11 +193,11 @@ def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data - - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. - - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. + - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. ## Filter Examples ```json @@ -456,11 +456,11 @@ async def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data - - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. - - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. + - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. ## Filter Examples ```json diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py index f6502014e..017199252 100644 --- a/langfuse/api/observations/raw_client.py +++ b/langfuse/api/observations/raw_client.py @@ -191,11 +191,11 @@ def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data - - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. - - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. + - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. ## Filter Examples ```json @@ -513,11 +513,11 @@ async def get_many( - `promptVersion` (number) - Associated prompt version ### Structured Data - - `input` (string) - Observation input. Supports accelerated token search with the `matches` operator. - - `output` (string) - Observation output. Supports accelerated token search with the `matches` operator. + - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. + - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. - The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs token-based full-text search using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Use `contains` for substring semantics. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. + The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. ## Filter Examples ```json From 7ad529b9f0e2f85f7948cd880989cf54d855ce87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:26:22 +0200 Subject: [PATCH 290/296] chore(deps): bump the github-actions group with 2 updates (#1691) Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [github/codeql-action](https://github.com/github/codeql-action). Updates `actions/checkout` from 6.0.2 to 6.0.3 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10) Updates `github/codeql-action` from 4.36.0 to 4.36.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/7211b7c8077ea37d8641b6271f6a365a22a5fbfa...87557b9c84dde89fdd9b10e88954ac2f4248e463) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action dependency-version: 4.36.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/codeql.yml | 6 +++--- .github/workflows/release.yml | 2 +- .github/workflows/zizmor.yml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4fca874d..e45d58f59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: linting: runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Install uv and set Python version @@ -37,7 +37,7 @@ jobs: type-checking: runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Install uv and set Python version @@ -78,7 +78,7 @@ jobs: name: Unit tests on Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Install uv and set Python version @@ -141,7 +141,7 @@ jobs: name: ${{ matrix.job_name }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Install uv and set Python version diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b10796c71..d9f44e50c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -55,13 +55,13 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 + uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -89,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 + uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e764bd94..acc942b8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,7 +90,7 @@ jobs: INPUTS_CONFIRM_MAJOR: ${{ inputs.confirm_major }} - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 token: ${{ secrets.GH_ACCESS_TOKEN }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 60e1b2952..4caa8f5dd 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -22,7 +22,7 @@ jobs: security-events: write steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Run zizmor From 7b427ba51bb3625d349c4f75ecddbcd4a754ce1e Mon Sep 17 00:00:00 2001 From: devteamaegis Date: Tue, 9 Jun 2026 10:33:14 -0400 Subject: [PATCH 291/296] fix(serializer): correctly serialize float('-inf') as "-Infinity" (#1683) * fix(serializer): correctly serialize float('-inf') as "-Infinity" * test(serializer): add test for float('-inf') serialization --------- Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> --- langfuse/_utils/serializer.py | 2 +- tests/unit/test_serializer.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 24c19aa12..27294bf80 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -76,7 +76,7 @@ def _default_inner(self, obj: Any) -> Any: return "NaN" if isinstance(obj, float) and math.isinf(obj): - return "Infinity" + return "-Infinity" if obj < 0 else "Infinity" if isinstance(obj, (Exception, KeyboardInterrupt)): return f"{type(obj).__name__}: {str(obj)}" diff --git a/tests/unit/test_serializer.py b/tests/unit/test_serializer.py index ce9462e04..f4c8dde86 100644 --- a/tests/unit/test_serializer.py +++ b/tests/unit/test_serializer.py @@ -165,6 +165,12 @@ def test_none(): assert serializer.encode(None) == "null" +def test_infinity_floats(): + serializer = EventSerializer() + assert serializer.encode(float("inf")) == '"Infinity"' + assert serializer.encode(float("-inf")) == '"-Infinity"' + + def test_slots(): class SlotClass: __slots__ = ["field"] From da69e63a1262a88fed2adb7545a7b565f981946e Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Tue, 9 Jun 2026 16:59:23 +0200 Subject: [PATCH 292/296] feat(api): update API spec from langfuse/langfuse c82119e (#1693) Co-authored-by: langfuse-bot --- langfuse/api/__init__.py | 74 +++ langfuse/api/client.py | 19 + langfuse/api/scores_v3/__init__.py | 112 +++++ langfuse/api/scores_v3/client.py | 341 +++++++++++++ langfuse/api/scores_v3/raw_client.py | 460 ++++++++++++++++++ langfuse/api/scores_v3/types/__init__.py | 114 +++++ langfuse/api/scores_v3/types/base_score_v3.py | 71 +++ .../api/scores_v3/types/boolean_score_v3.py | 17 + .../scores_v3/types/categorical_score_v3.py | 17 + .../scores_v3/types/correction_score_v3.py | 17 + .../api/scores_v3/types/get_scores_v3meta.py | 18 + .../scores_v3/types/get_scores_v3response.py | 17 + .../api/scores_v3/types/numeric_score_v3.py | 17 + .../types/score_subject_experiment_v3.py | 17 + .../types/score_subject_observation_v3.py | 26 + .../types/score_subject_session_v3.py | 17 + .../scores_v3/types/score_subject_trace_v3.py | 17 + .../api/scores_v3/types/score_subject_v3.py | 76 +++ langfuse/api/scores_v3/types/score_v3.py | 200 ++++++++ langfuse/api/scores_v3/types/text_score_v3.py | 17 + langfuse/api/unstable/__init__.py | 51 +- langfuse/api/unstable/commons/__init__.py | 3 + .../api/unstable/commons/types/__init__.py | 3 + .../code_evaluator_source_code_language.py | 26 + .../commons/types/evaluation_rule_mapping.py | 6 +- .../unstable/commons/types/evaluator_type.py | 11 +- .../api/unstable/evaluation_rules/__init__.py | 15 + .../api/unstable/evaluation_rules/client.py | 257 ++++------ .../unstable/evaluation_rules/raw_client.py | 177 ++----- .../evaluation_rules/types/__init__.py | 21 + ...ode_evaluation_rule_evaluator_reference.py | 32 ++ .../create_code_evaluation_rule_request.py | 56 +++ .../types/create_evaluation_rule_request.py | 79 +-- ...te_llm_as_judge_evaluation_rule_request.py | 65 +++ .../evaluation_rules/types/evaluation_rule.py | 4 +- .../types/evaluation_rule_evaluator.py | 8 +- .../evaluation_rule_evaluator_reference.py | 5 +- ...dge_evaluation_rule_evaluator_reference.py | 33 ++ .../types/llm_as_judge_evaluator_type.py | 15 + .../types/update_evaluation_rule_request.py | 10 +- langfuse/api/unstable/evaluators/__init__.py | 39 +- langfuse/api/unstable/evaluators/client.py | 131 +++-- .../api/unstable/evaluators/raw_client.py | 99 ++-- .../api/unstable/evaluators/types/__init__.py | 37 +- .../evaluators/types/code_evaluator.py | 31 ++ .../types/create_code_evaluator_request.py | 36 ++ .../types/create_evaluator_request.py | 58 ++- .../create_llm_as_judge_evaluator_request.py | 43 ++ .../unstable/evaluators/types/evaluator.py | 132 +++-- .../evaluators/types/evaluator_base.py | 64 +++ .../types/llm_as_judge_evaluator.py | 40 ++ 51 files changed, 2636 insertions(+), 615 deletions(-) create mode 100644 langfuse/api/scores_v3/__init__.py create mode 100644 langfuse/api/scores_v3/client.py create mode 100644 langfuse/api/scores_v3/raw_client.py create mode 100644 langfuse/api/scores_v3/types/__init__.py create mode 100644 langfuse/api/scores_v3/types/base_score_v3.py create mode 100644 langfuse/api/scores_v3/types/boolean_score_v3.py create mode 100644 langfuse/api/scores_v3/types/categorical_score_v3.py create mode 100644 langfuse/api/scores_v3/types/correction_score_v3.py create mode 100644 langfuse/api/scores_v3/types/get_scores_v3meta.py create mode 100644 langfuse/api/scores_v3/types/get_scores_v3response.py create mode 100644 langfuse/api/scores_v3/types/numeric_score_v3.py create mode 100644 langfuse/api/scores_v3/types/score_subject_experiment_v3.py create mode 100644 langfuse/api/scores_v3/types/score_subject_observation_v3.py create mode 100644 langfuse/api/scores_v3/types/score_subject_session_v3.py create mode 100644 langfuse/api/scores_v3/types/score_subject_trace_v3.py create mode 100644 langfuse/api/scores_v3/types/score_subject_v3.py create mode 100644 langfuse/api/scores_v3/types/score_v3.py create mode 100644 langfuse/api/scores_v3/types/text_score_v3.py create mode 100644 langfuse/api/unstable/commons/types/code_evaluator_source_code_language.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/code_evaluation_rule_evaluator_reference.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/create_code_evaluation_rule_request.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/create_llm_as_judge_evaluation_rule_request.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluation_rule_evaluator_reference.py create mode 100644 langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluator_type.py create mode 100644 langfuse/api/unstable/evaluators/types/code_evaluator.py create mode 100644 langfuse/api/unstable/evaluators/types/create_code_evaluator_request.py create mode 100644 langfuse/api/unstable/evaluators/types/create_llm_as_judge_evaluator_request.py create mode 100644 langfuse/api/unstable/evaluators/types/evaluator_base.py create mode 100644 langfuse/api/unstable/evaluators/types/llm_as_judge_evaluator.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 0e036263a..46985c0b9 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -30,6 +30,7 @@ scim, score_configs, scores, + scores_v3, sessions, trace, unstable, @@ -297,6 +298,31 @@ GetScoresResponseData_Text, GetScoresResponseTraceData, ) + from .scores_v3 import ( + BaseScoreV3, + BooleanScoreV3, + CategoricalScoreV3, + CorrectionScoreV3, + GetScoresV3Meta, + GetScoresV3Response, + NumericScoreV3, + ScoreSubjectExperimentV3, + ScoreSubjectObservationV3, + ScoreSubjectSessionV3, + ScoreSubjectTraceV3, + ScoreSubjectV3, + ScoreSubjectV3_Experiment, + ScoreSubjectV3_Observation, + ScoreSubjectV3_Session, + ScoreSubjectV3_Trace, + ScoreV3, + ScoreV3_Boolean, + ScoreV3_Categorical, + ScoreV3_Correction, + ScoreV3_Numeric, + ScoreV3_Text, + TextScoreV3, + ) from .sessions import PaginatedSessions from .trace import DeleteTraceResponse, Sort, Traces _dynamic_imports: typing.Dict[str, str] = { @@ -316,6 +342,7 @@ "BasePrompt": ".prompts", "BaseScore": ".commons", "BaseScoreV1": ".commons", + "BaseScoreV3": ".scores_v3", "BlobStorageExportFieldGroup": ".blob_storage_integrations", "BlobStorageExportFrequency": ".blob_storage_integrations", "BlobStorageExportMode": ".blob_storage_integrations", @@ -329,9 +356,11 @@ "BlobStorageSyncStatus": ".blob_storage_integrations", "BooleanScore": ".commons", "BooleanScoreV1": ".commons", + "BooleanScoreV3": ".scores_v3", "BulkConfig": ".scim", "CategoricalScore": ".commons", "CategoricalScoreV1": ".commons", + "CategoricalScoreV3": ".scores_v3", "ChatMessage": ".prompts", "ChatMessageType": ".prompts", "ChatMessageWithPlaceholders": ".prompts", @@ -340,6 +369,7 @@ "CommentObjectType": ".commons", "ConfigCategory": ".commons", "CorrectionScore": ".commons", + "CorrectionScoreV3": ".scores_v3", "CreateAnnotationQueueAssignmentResponse": ".annotation_queues", "CreateAnnotationQueueItemRequest": ".annotation_queues", "CreateAnnotationQueueRequest": ".annotation_queues", @@ -397,6 +427,8 @@ "GetScoresResponseData_Numeric": ".scores", "GetScoresResponseData_Text": ".scores", "GetScoresResponseTraceData": ".scores", + "GetScoresV3Meta": ".scores_v3", + "GetScoresV3Response": ".scores_v3", "HealthResponse": ".health", "IngestionError": ".ingestion", "IngestionEvent": ".ingestion", @@ -431,6 +463,7 @@ "NotFoundError": ".commons", "NumericScore": ".commons", "NumericScoreV1": ".commons", + "NumericScoreV3": ".scores_v3", "Observation": ".commons", "ObservationBody": ".ingestion", "ObservationLevel": ".commons", @@ -500,11 +533,26 @@ "ScoreDataType": ".commons", "ScoreEvent": ".ingestion", "ScoreSource": ".commons", + "ScoreSubjectExperimentV3": ".scores_v3", + "ScoreSubjectObservationV3": ".scores_v3", + "ScoreSubjectSessionV3": ".scores_v3", + "ScoreSubjectTraceV3": ".scores_v3", + "ScoreSubjectV3": ".scores_v3", + "ScoreSubjectV3_Experiment": ".scores_v3", + "ScoreSubjectV3_Observation": ".scores_v3", + "ScoreSubjectV3_Session": ".scores_v3", + "ScoreSubjectV3_Trace": ".scores_v3", "ScoreV1": ".commons", "ScoreV1_Boolean": ".commons", "ScoreV1_Categorical": ".commons", "ScoreV1_Numeric": ".commons", "ScoreV1_Text": ".commons", + "ScoreV3": ".scores_v3", + "ScoreV3_Boolean": ".scores_v3", + "ScoreV3_Categorical": ".scores_v3", + "ScoreV3_Correction": ".scores_v3", + "ScoreV3_Numeric": ".scores_v3", + "ScoreV3_Text": ".scores_v3", "Score_Boolean": ".commons", "Score_Categorical": ".commons", "Score_Correction": ".commons", @@ -520,6 +568,7 @@ "TextPrompt": ".prompts", "TextScore": ".commons", "TextScoreV1": ".commons", + "TextScoreV3": ".scores_v3", "Trace": ".commons", "TraceBody": ".ingestion", "TraceEvent": ".ingestion", @@ -562,6 +611,7 @@ "scim": ".scim", "score_configs": ".score_configs", "scores": ".scores", + "scores_v3": ".scores_v3", "sessions": ".sessions", "trace": ".trace", "unstable": ".unstable", @@ -613,6 +663,7 @@ def __dir__(): "BasePrompt", "BaseScore", "BaseScoreV1", + "BaseScoreV3", "BlobStorageExportFieldGroup", "BlobStorageExportFrequency", "BlobStorageExportMode", @@ -626,9 +677,11 @@ def __dir__(): "BlobStorageSyncStatus", "BooleanScore", "BooleanScoreV1", + "BooleanScoreV3", "BulkConfig", "CategoricalScore", "CategoricalScoreV1", + "CategoricalScoreV3", "ChatMessage", "ChatMessageType", "ChatMessageWithPlaceholders", @@ -637,6 +690,7 @@ def __dir__(): "CommentObjectType", "ConfigCategory", "CorrectionScore", + "CorrectionScoreV3", "CreateAnnotationQueueAssignmentResponse", "CreateAnnotationQueueItemRequest", "CreateAnnotationQueueRequest", @@ -694,6 +748,8 @@ def __dir__(): "GetScoresResponseData_Numeric", "GetScoresResponseData_Text", "GetScoresResponseTraceData", + "GetScoresV3Meta", + "GetScoresV3Response", "HealthResponse", "IngestionError", "IngestionEvent", @@ -728,6 +784,7 @@ def __dir__(): "NotFoundError", "NumericScore", "NumericScoreV1", + "NumericScoreV3", "Observation", "ObservationBody", "ObservationLevel", @@ -797,11 +854,26 @@ def __dir__(): "ScoreDataType", "ScoreEvent", "ScoreSource", + "ScoreSubjectExperimentV3", + "ScoreSubjectObservationV3", + "ScoreSubjectSessionV3", + "ScoreSubjectTraceV3", + "ScoreSubjectV3", + "ScoreSubjectV3_Experiment", + "ScoreSubjectV3_Observation", + "ScoreSubjectV3_Session", + "ScoreSubjectV3_Trace", "ScoreV1", "ScoreV1_Boolean", "ScoreV1_Categorical", "ScoreV1_Numeric", "ScoreV1_Text", + "ScoreV3", + "ScoreV3_Boolean", + "ScoreV3_Categorical", + "ScoreV3_Correction", + "ScoreV3_Numeric", + "ScoreV3_Text", "Score_Boolean", "Score_Categorical", "Score_Correction", @@ -817,6 +889,7 @@ def __dir__(): "TextPrompt", "TextScore", "TextScoreV1", + "TextScoreV3", "Trace", "TraceBody", "TraceEvent", @@ -859,6 +932,7 @@ def __dir__(): "scim", "score_configs", "scores", + "scores_v3", "sessions", "trace", "unstable", diff --git a/langfuse/api/client.py b/langfuse/api/client.py index c0413704b..a72aede85 100644 --- a/langfuse/api/client.py +++ b/langfuse/api/client.py @@ -39,6 +39,7 @@ from .scim.client import AsyncScimClient, ScimClient from .score_configs.client import AsyncScoreConfigsClient, ScoreConfigsClient from .scores.client import AsyncScoresClient, ScoresClient + from .scores_v3.client import AsyncScoresV3Client, ScoresV3Client from .sessions.client import AsyncSessionsClient, SessionsClient from .trace.client import AsyncTraceClient, TraceClient from .unstable.client import AsyncUnstableClient, UnstableClient @@ -145,6 +146,7 @@ def __init__( self._prompts: typing.Optional[PromptsClient] = None self._scim: typing.Optional[ScimClient] = None self._score_configs: typing.Optional[ScoreConfigsClient] = None + self._scores_v3: typing.Optional[ScoresV3Client] = None self._scores: typing.Optional[ScoresClient] = None self._sessions: typing.Optional[SessionsClient] = None self._trace: typing.Optional[TraceClient] = None @@ -336,6 +338,14 @@ def score_configs(self): ) return self._score_configs + @property + def scores_v3(self): + if self._scores_v3 is None: + from .scores_v3.client import ScoresV3Client # noqa: E402 + + self._scores_v3 = ScoresV3Client(client_wrapper=self._client_wrapper) + return self._scores_v3 + @property def scores(self): if self._scores is None: @@ -470,6 +480,7 @@ def __init__( self._prompts: typing.Optional[AsyncPromptsClient] = None self._scim: typing.Optional[AsyncScimClient] = None self._score_configs: typing.Optional[AsyncScoreConfigsClient] = None + self._scores_v3: typing.Optional[AsyncScoresV3Client] = None self._scores: typing.Optional[AsyncScoresClient] = None self._sessions: typing.Optional[AsyncSessionsClient] = None self._trace: typing.Optional[AsyncTraceClient] = None @@ -665,6 +676,14 @@ def score_configs(self): ) return self._score_configs + @property + def scores_v3(self): + if self._scores_v3 is None: + from .scores_v3.client import AsyncScoresV3Client # noqa: E402 + + self._scores_v3 = AsyncScoresV3Client(client_wrapper=self._client_wrapper) + return self._scores_v3 + @property def scores(self): if self._scores is None: diff --git a/langfuse/api/scores_v3/__init__.py b/langfuse/api/scores_v3/__init__.py new file mode 100644 index 000000000..855868335 --- /dev/null +++ b/langfuse/api/scores_v3/__init__.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + BaseScoreV3, + BooleanScoreV3, + CategoricalScoreV3, + CorrectionScoreV3, + GetScoresV3Meta, + GetScoresV3Response, + NumericScoreV3, + ScoreSubjectExperimentV3, + ScoreSubjectObservationV3, + ScoreSubjectSessionV3, + ScoreSubjectTraceV3, + ScoreSubjectV3, + ScoreSubjectV3_Experiment, + ScoreSubjectV3_Observation, + ScoreSubjectV3_Session, + ScoreSubjectV3_Trace, + ScoreV3, + ScoreV3_Boolean, + ScoreV3_Categorical, + ScoreV3_Correction, + ScoreV3_Numeric, + ScoreV3_Text, + TextScoreV3, + ) +_dynamic_imports: typing.Dict[str, str] = { + "BaseScoreV3": ".types", + "BooleanScoreV3": ".types", + "CategoricalScoreV3": ".types", + "CorrectionScoreV3": ".types", + "GetScoresV3Meta": ".types", + "GetScoresV3Response": ".types", + "NumericScoreV3": ".types", + "ScoreSubjectExperimentV3": ".types", + "ScoreSubjectObservationV3": ".types", + "ScoreSubjectSessionV3": ".types", + "ScoreSubjectTraceV3": ".types", + "ScoreSubjectV3": ".types", + "ScoreSubjectV3_Experiment": ".types", + "ScoreSubjectV3_Observation": ".types", + "ScoreSubjectV3_Session": ".types", + "ScoreSubjectV3_Trace": ".types", + "ScoreV3": ".types", + "ScoreV3_Boolean": ".types", + "ScoreV3_Categorical": ".types", + "ScoreV3_Correction": ".types", + "ScoreV3_Numeric": ".types", + "ScoreV3_Text": ".types", + "TextScoreV3": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BaseScoreV3", + "BooleanScoreV3", + "CategoricalScoreV3", + "CorrectionScoreV3", + "GetScoresV3Meta", + "GetScoresV3Response", + "NumericScoreV3", + "ScoreSubjectExperimentV3", + "ScoreSubjectObservationV3", + "ScoreSubjectSessionV3", + "ScoreSubjectTraceV3", + "ScoreSubjectV3", + "ScoreSubjectV3_Experiment", + "ScoreSubjectV3_Observation", + "ScoreSubjectV3_Session", + "ScoreSubjectV3_Trace", + "ScoreV3", + "ScoreV3_Boolean", + "ScoreV3_Categorical", + "ScoreV3_Correction", + "ScoreV3_Numeric", + "ScoreV3_Text", + "TextScoreV3", +] diff --git a/langfuse/api/scores_v3/client.py b/langfuse/api/scores_v3/client.py new file mode 100644 index 000000000..2755d3e74 --- /dev/null +++ b/langfuse/api/scores_v3/client.py @@ -0,0 +1,341 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawScoresV3Client, RawScoresV3Client +from .types.get_scores_v3response import GetScoresV3Response + + +class ScoresV3Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawScoresV3Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawScoresV3Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawScoresV3Client + """ + return self._raw_client + + def get_many_v3( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + fields: typing.Optional[str] = None, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + source: typing.Optional[str] = None, + data_type: typing.Optional[str] = None, + environment: typing.Optional[str] = None, + config_id: typing.Optional[str] = None, + queue_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + value: typing.Optional[str] = None, + value_min: typing.Optional[float] = None, + value_max: typing.Optional[float] = None, + trace_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, + experiment_id: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetScoresV3Response: + """ + Get a list of scores with a polymorphic `value` field (v3). + + This endpoint requires Langfuse v4 or later. + + The `value` field type depends on `dataType`: + - `NUMERIC` → number + - `BOOLEAN` → boolean + - `CATEGORICAL`, `TEXT`, `CORRECTION` → string + + Use the `fields` parameter to include optional field groups beyond the + default `core`. Unknown group names return HTTP 400. + + Parameters + ---------- + limit : typing.Optional[int] + Number of items per page. Maximum 100, default 50. Requests with a limit greater than 100 return HTTP 400. + + cursor : typing.Optional[str] + URL-safe base64 (base64url) cursor for pagination. Use the cursor from the previous response to get the next page. Absent on the final page. + + fields : typing.Optional[str] + Comma-separated field groups to include. Allowed: core, details, subject, annotation. Defaults to "core". Unknown names return HTTP 400. + + id : typing.Optional[str] + Comma-separated list of score IDs to filter by (OR within, AND across filters). + + name : typing.Optional[str] + Comma-separated list of score names to filter by. + + source : typing.Optional[str] + Comma-separated list of score sources to filter by (e.g. API, ANNOTATION, EVAL). Case-insensitive — `api` and `API` are equivalent. + + data_type : typing.Optional[str] + Comma-separated list of data types to filter by (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, CORRECTION). Case-insensitive — `numeric` and `NUMERIC` are equivalent. Must be a single value when used with value, valueMin, or valueMax; otherwise the request returns HTTP 400. Must be NUMERIC when used with valueMin or valueMax. + + environment : typing.Optional[str] + Comma-separated list of environments to filter by. + + config_id : typing.Optional[str] + Comma-separated list of score config IDs to filter by. + + queue_id : typing.Optional[str] + Comma-separated list of annotation queue IDs to filter by. + + author_user_id : typing.Optional[str] + Comma-separated list of author user IDs to filter by. + + value : typing.Optional[str] + Comma-separated list of exact values to filter by. Requires a single dataType from NUMERIC, BOOLEAN, or CATEGORICAL; any other dataType, multiple dataTypes, or omitting dataType returns HTTP 400. For BOOLEAN, each value must be "true" or "false"; for NUMERIC, each value must be a finite number. Otherwise the request returns HTTP 400. + + value_min : typing.Optional[float] + Inclusive lower bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + value_max : typing.Optional[float] + Inclusive upper bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + trace_id : typing.Optional[str] + Comma-separated list of trace IDs to filter by. Mutually exclusive with sessionId, experimentId. May be combined with observationId to scope the observation lookup to a specific trace. + + session_id : typing.Optional[str] + Comma-separated list of session IDs to filter by. Mutually exclusive with traceId, observationId, experimentId. + + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter by. Requires traceId to be specified, because observation IDs are scoped to a trace. Mutually exclusive with sessionId, experimentId. Returns HTTP 400 when used without traceId. + + experiment_id : typing.Optional[str] + Comma-separated list of dataset run IDs (experiment IDs) to filter by. Mutually exclusive with traceId, sessionId, observationId. + + from_timestamp : typing.Optional[dt.datetime] + Inclusive lower bound on the score timestamp. + + to_timestamp : typing.Optional[dt.datetime] + Exclusive upper bound on the score timestamp. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetScoresV3Response + + Examples + -------- + from langfuse import LangfuseAPI + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.scores_v3.get_many_v3() + """ + _response = self._raw_client.get_many_v3( + limit=limit, + cursor=cursor, + fields=fields, + id=id, + name=name, + source=source, + data_type=data_type, + environment=environment, + config_id=config_id, + queue_id=queue_id, + author_user_id=author_user_id, + value=value, + value_min=value_min, + value_max=value_max, + trace_id=trace_id, + session_id=session_id, + observation_id=observation_id, + experiment_id=experiment_id, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + request_options=request_options, + ) + return _response.data + + +class AsyncScoresV3Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawScoresV3Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawScoresV3Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawScoresV3Client + """ + return self._raw_client + + async def get_many_v3( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + fields: typing.Optional[str] = None, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + source: typing.Optional[str] = None, + data_type: typing.Optional[str] = None, + environment: typing.Optional[str] = None, + config_id: typing.Optional[str] = None, + queue_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + value: typing.Optional[str] = None, + value_min: typing.Optional[float] = None, + value_max: typing.Optional[float] = None, + trace_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, + experiment_id: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetScoresV3Response: + """ + Get a list of scores with a polymorphic `value` field (v3). + + This endpoint requires Langfuse v4 or later. + + The `value` field type depends on `dataType`: + - `NUMERIC` → number + - `BOOLEAN` → boolean + - `CATEGORICAL`, `TEXT`, `CORRECTION` → string + + Use the `fields` parameter to include optional field groups beyond the + default `core`. Unknown group names return HTTP 400. + + Parameters + ---------- + limit : typing.Optional[int] + Number of items per page. Maximum 100, default 50. Requests with a limit greater than 100 return HTTP 400. + + cursor : typing.Optional[str] + URL-safe base64 (base64url) cursor for pagination. Use the cursor from the previous response to get the next page. Absent on the final page. + + fields : typing.Optional[str] + Comma-separated field groups to include. Allowed: core, details, subject, annotation. Defaults to "core". Unknown names return HTTP 400. + + id : typing.Optional[str] + Comma-separated list of score IDs to filter by (OR within, AND across filters). + + name : typing.Optional[str] + Comma-separated list of score names to filter by. + + source : typing.Optional[str] + Comma-separated list of score sources to filter by (e.g. API, ANNOTATION, EVAL). Case-insensitive — `api` and `API` are equivalent. + + data_type : typing.Optional[str] + Comma-separated list of data types to filter by (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, CORRECTION). Case-insensitive — `numeric` and `NUMERIC` are equivalent. Must be a single value when used with value, valueMin, or valueMax; otherwise the request returns HTTP 400. Must be NUMERIC when used with valueMin or valueMax. + + environment : typing.Optional[str] + Comma-separated list of environments to filter by. + + config_id : typing.Optional[str] + Comma-separated list of score config IDs to filter by. + + queue_id : typing.Optional[str] + Comma-separated list of annotation queue IDs to filter by. + + author_user_id : typing.Optional[str] + Comma-separated list of author user IDs to filter by. + + value : typing.Optional[str] + Comma-separated list of exact values to filter by. Requires a single dataType from NUMERIC, BOOLEAN, or CATEGORICAL; any other dataType, multiple dataTypes, or omitting dataType returns HTTP 400. For BOOLEAN, each value must be "true" or "false"; for NUMERIC, each value must be a finite number. Otherwise the request returns HTTP 400. + + value_min : typing.Optional[float] + Inclusive lower bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + value_max : typing.Optional[float] + Inclusive upper bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + trace_id : typing.Optional[str] + Comma-separated list of trace IDs to filter by. Mutually exclusive with sessionId, experimentId. May be combined with observationId to scope the observation lookup to a specific trace. + + session_id : typing.Optional[str] + Comma-separated list of session IDs to filter by. Mutually exclusive with traceId, observationId, experimentId. + + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter by. Requires traceId to be specified, because observation IDs are scoped to a trace. Mutually exclusive with sessionId, experimentId. Returns HTTP 400 when used without traceId. + + experiment_id : typing.Optional[str] + Comma-separated list of dataset run IDs (experiment IDs) to filter by. Mutually exclusive with traceId, sessionId, observationId. + + from_timestamp : typing.Optional[dt.datetime] + Inclusive lower bound on the score timestamp. + + to_timestamp : typing.Optional[dt.datetime] + Exclusive upper bound on the score timestamp. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetScoresV3Response + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.scores_v3.get_many_v3() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_many_v3( + limit=limit, + cursor=cursor, + fields=fields, + id=id, + name=name, + source=source, + data_type=data_type, + environment=environment, + config_id=config_id, + queue_id=queue_id, + author_user_id=author_user_id, + value=value, + value_min=value_min, + value_max=value_max, + trace_id=trace_id, + session_id=session_id, + observation_id=observation_id, + experiment_id=experiment_id, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/scores_v3/raw_client.py b/langfuse/api/scores_v3/raw_client.py new file mode 100644 index 000000000..47c9f3f8d --- /dev/null +++ b/langfuse/api/scores_v3/raw_client.py @@ -0,0 +1,460 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..commons.errors.access_denied_error import AccessDeniedError +from ..commons.errors.error import Error +from ..commons.errors.method_not_allowed_error import MethodNotAllowedError +from ..commons.errors.not_found_error import NotFoundError +from ..commons.errors.unauthorized_error import UnauthorizedError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.datetime_utils import serialize_datetime +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.get_scores_v3response import GetScoresV3Response + + +class RawScoresV3Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_many_v3( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + fields: typing.Optional[str] = None, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + source: typing.Optional[str] = None, + data_type: typing.Optional[str] = None, + environment: typing.Optional[str] = None, + config_id: typing.Optional[str] = None, + queue_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + value: typing.Optional[str] = None, + value_min: typing.Optional[float] = None, + value_max: typing.Optional[float] = None, + trace_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, + experiment_id: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GetScoresV3Response]: + """ + Get a list of scores with a polymorphic `value` field (v3). + + This endpoint requires Langfuse v4 or later. + + The `value` field type depends on `dataType`: + - `NUMERIC` → number + - `BOOLEAN` → boolean + - `CATEGORICAL`, `TEXT`, `CORRECTION` → string + + Use the `fields` parameter to include optional field groups beyond the + default `core`. Unknown group names return HTTP 400. + + Parameters + ---------- + limit : typing.Optional[int] + Number of items per page. Maximum 100, default 50. Requests with a limit greater than 100 return HTTP 400. + + cursor : typing.Optional[str] + URL-safe base64 (base64url) cursor for pagination. Use the cursor from the previous response to get the next page. Absent on the final page. + + fields : typing.Optional[str] + Comma-separated field groups to include. Allowed: core, details, subject, annotation. Defaults to "core". Unknown names return HTTP 400. + + id : typing.Optional[str] + Comma-separated list of score IDs to filter by (OR within, AND across filters). + + name : typing.Optional[str] + Comma-separated list of score names to filter by. + + source : typing.Optional[str] + Comma-separated list of score sources to filter by (e.g. API, ANNOTATION, EVAL). Case-insensitive — `api` and `API` are equivalent. + + data_type : typing.Optional[str] + Comma-separated list of data types to filter by (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, CORRECTION). Case-insensitive — `numeric` and `NUMERIC` are equivalent. Must be a single value when used with value, valueMin, or valueMax; otherwise the request returns HTTP 400. Must be NUMERIC when used with valueMin or valueMax. + + environment : typing.Optional[str] + Comma-separated list of environments to filter by. + + config_id : typing.Optional[str] + Comma-separated list of score config IDs to filter by. + + queue_id : typing.Optional[str] + Comma-separated list of annotation queue IDs to filter by. + + author_user_id : typing.Optional[str] + Comma-separated list of author user IDs to filter by. + + value : typing.Optional[str] + Comma-separated list of exact values to filter by. Requires a single dataType from NUMERIC, BOOLEAN, or CATEGORICAL; any other dataType, multiple dataTypes, or omitting dataType returns HTTP 400. For BOOLEAN, each value must be "true" or "false"; for NUMERIC, each value must be a finite number. Otherwise the request returns HTTP 400. + + value_min : typing.Optional[float] + Inclusive lower bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + value_max : typing.Optional[float] + Inclusive upper bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + trace_id : typing.Optional[str] + Comma-separated list of trace IDs to filter by. Mutually exclusive with sessionId, experimentId. May be combined with observationId to scope the observation lookup to a specific trace. + + session_id : typing.Optional[str] + Comma-separated list of session IDs to filter by. Mutually exclusive with traceId, observationId, experimentId. + + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter by. Requires traceId to be specified, because observation IDs are scoped to a trace. Mutually exclusive with sessionId, experimentId. Returns HTTP 400 when used without traceId. + + experiment_id : typing.Optional[str] + Comma-separated list of dataset run IDs (experiment IDs) to filter by. Mutually exclusive with traceId, sessionId, observationId. + + from_timestamp : typing.Optional[dt.datetime] + Inclusive lower bound on the score timestamp. + + to_timestamp : typing.Optional[dt.datetime] + Exclusive upper bound on the score timestamp. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetScoresV3Response] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/v3/scores", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "fields": fields, + "id": id, + "name": name, + "source": source, + "dataType": data_type, + "environment": environment, + "configId": config_id, + "queueId": queue_id, + "authorUserId": author_user_id, + "value": value, + "valueMin": value_min, + "valueMax": value_max, + "traceId": trace_id, + "sessionId": session_id, + "observationId": observation_id, + "experimentId": experiment_id, + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetScoresV3Response, + parse_obj_as( + type_=GetScoresV3Response, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawScoresV3Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_many_v3( + self, + *, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + fields: typing.Optional[str] = None, + id: typing.Optional[str] = None, + name: typing.Optional[str] = None, + source: typing.Optional[str] = None, + data_type: typing.Optional[str] = None, + environment: typing.Optional[str] = None, + config_id: typing.Optional[str] = None, + queue_id: typing.Optional[str] = None, + author_user_id: typing.Optional[str] = None, + value: typing.Optional[str] = None, + value_min: typing.Optional[float] = None, + value_max: typing.Optional[float] = None, + trace_id: typing.Optional[str] = None, + session_id: typing.Optional[str] = None, + observation_id: typing.Optional[str] = None, + experiment_id: typing.Optional[str] = None, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GetScoresV3Response]: + """ + Get a list of scores with a polymorphic `value` field (v3). + + This endpoint requires Langfuse v4 or later. + + The `value` field type depends on `dataType`: + - `NUMERIC` → number + - `BOOLEAN` → boolean + - `CATEGORICAL`, `TEXT`, `CORRECTION` → string + + Use the `fields` parameter to include optional field groups beyond the + default `core`. Unknown group names return HTTP 400. + + Parameters + ---------- + limit : typing.Optional[int] + Number of items per page. Maximum 100, default 50. Requests with a limit greater than 100 return HTTP 400. + + cursor : typing.Optional[str] + URL-safe base64 (base64url) cursor for pagination. Use the cursor from the previous response to get the next page. Absent on the final page. + + fields : typing.Optional[str] + Comma-separated field groups to include. Allowed: core, details, subject, annotation. Defaults to "core". Unknown names return HTTP 400. + + id : typing.Optional[str] + Comma-separated list of score IDs to filter by (OR within, AND across filters). + + name : typing.Optional[str] + Comma-separated list of score names to filter by. + + source : typing.Optional[str] + Comma-separated list of score sources to filter by (e.g. API, ANNOTATION, EVAL). Case-insensitive — `api` and `API` are equivalent. + + data_type : typing.Optional[str] + Comma-separated list of data types to filter by (NUMERIC, BOOLEAN, CATEGORICAL, TEXT, CORRECTION). Case-insensitive — `numeric` and `NUMERIC` are equivalent. Must be a single value when used with value, valueMin, or valueMax; otherwise the request returns HTTP 400. Must be NUMERIC when used with valueMin or valueMax. + + environment : typing.Optional[str] + Comma-separated list of environments to filter by. + + config_id : typing.Optional[str] + Comma-separated list of score config IDs to filter by. + + queue_id : typing.Optional[str] + Comma-separated list of annotation queue IDs to filter by. + + author_user_id : typing.Optional[str] + Comma-separated list of author user IDs to filter by. + + value : typing.Optional[str] + Comma-separated list of exact values to filter by. Requires a single dataType from NUMERIC, BOOLEAN, or CATEGORICAL; any other dataType, multiple dataTypes, or omitting dataType returns HTTP 400. For BOOLEAN, each value must be "true" or "false"; for NUMERIC, each value must be a finite number. Otherwise the request returns HTTP 400. + + value_min : typing.Optional[float] + Inclusive lower bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + value_max : typing.Optional[float] + Inclusive upper bound on the numeric value. Requires dataType=NUMERIC as a single value; otherwise the request returns HTTP 400. + + trace_id : typing.Optional[str] + Comma-separated list of trace IDs to filter by. Mutually exclusive with sessionId, experimentId. May be combined with observationId to scope the observation lookup to a specific trace. + + session_id : typing.Optional[str] + Comma-separated list of session IDs to filter by. Mutually exclusive with traceId, observationId, experimentId. + + observation_id : typing.Optional[str] + Comma-separated list of observation IDs to filter by. Requires traceId to be specified, because observation IDs are scoped to a trace. Mutually exclusive with sessionId, experimentId. Returns HTTP 400 when used without traceId. + + experiment_id : typing.Optional[str] + Comma-separated list of dataset run IDs (experiment IDs) to filter by. Mutually exclusive with traceId, sessionId, observationId. + + from_timestamp : typing.Optional[dt.datetime] + Inclusive lower bound on the score timestamp. + + to_timestamp : typing.Optional[dt.datetime] + Exclusive upper bound on the score timestamp. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetScoresV3Response] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/v3/scores", + method="GET", + params={ + "limit": limit, + "cursor": cursor, + "fields": fields, + "id": id, + "name": name, + "source": source, + "dataType": data_type, + "environment": environment, + "configId": config_id, + "queueId": queue_id, + "authorUserId": author_user_id, + "value": value, + "valueMin": value_min, + "valueMax": value_max, + "traceId": trace_id, + "sessionId": session_id, + "observationId": observation_id, + "experimentId": experiment_id, + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetScoresV3Response, + parse_obj_as( + type_=GetScoresV3Response, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/scores_v3/types/__init__.py b/langfuse/api/scores_v3/types/__init__.py new file mode 100644 index 000000000..14da0ca73 --- /dev/null +++ b/langfuse/api/scores_v3/types/__init__.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .base_score_v3 import BaseScoreV3 + from .boolean_score_v3 import BooleanScoreV3 + from .categorical_score_v3 import CategoricalScoreV3 + from .correction_score_v3 import CorrectionScoreV3 + from .get_scores_v3meta import GetScoresV3Meta + from .get_scores_v3response import GetScoresV3Response + from .numeric_score_v3 import NumericScoreV3 + from .score_subject_experiment_v3 import ScoreSubjectExperimentV3 + from .score_subject_observation_v3 import ScoreSubjectObservationV3 + from .score_subject_session_v3 import ScoreSubjectSessionV3 + from .score_subject_trace_v3 import ScoreSubjectTraceV3 + from .score_subject_v3 import ( + ScoreSubjectV3, + ScoreSubjectV3_Experiment, + ScoreSubjectV3_Observation, + ScoreSubjectV3_Session, + ScoreSubjectV3_Trace, + ) + from .score_v3 import ( + ScoreV3, + ScoreV3_Boolean, + ScoreV3_Categorical, + ScoreV3_Correction, + ScoreV3_Numeric, + ScoreV3_Text, + ) + from .text_score_v3 import TextScoreV3 +_dynamic_imports: typing.Dict[str, str] = { + "BaseScoreV3": ".base_score_v3", + "BooleanScoreV3": ".boolean_score_v3", + "CategoricalScoreV3": ".categorical_score_v3", + "CorrectionScoreV3": ".correction_score_v3", + "GetScoresV3Meta": ".get_scores_v3meta", + "GetScoresV3Response": ".get_scores_v3response", + "NumericScoreV3": ".numeric_score_v3", + "ScoreSubjectExperimentV3": ".score_subject_experiment_v3", + "ScoreSubjectObservationV3": ".score_subject_observation_v3", + "ScoreSubjectSessionV3": ".score_subject_session_v3", + "ScoreSubjectTraceV3": ".score_subject_trace_v3", + "ScoreSubjectV3": ".score_subject_v3", + "ScoreSubjectV3_Experiment": ".score_subject_v3", + "ScoreSubjectV3_Observation": ".score_subject_v3", + "ScoreSubjectV3_Session": ".score_subject_v3", + "ScoreSubjectV3_Trace": ".score_subject_v3", + "ScoreV3": ".score_v3", + "ScoreV3_Boolean": ".score_v3", + "ScoreV3_Categorical": ".score_v3", + "ScoreV3_Correction": ".score_v3", + "ScoreV3_Numeric": ".score_v3", + "ScoreV3_Text": ".score_v3", + "TextScoreV3": ".text_score_v3", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BaseScoreV3", + "BooleanScoreV3", + "CategoricalScoreV3", + "CorrectionScoreV3", + "GetScoresV3Meta", + "GetScoresV3Response", + "NumericScoreV3", + "ScoreSubjectExperimentV3", + "ScoreSubjectObservationV3", + "ScoreSubjectSessionV3", + "ScoreSubjectTraceV3", + "ScoreSubjectV3", + "ScoreSubjectV3_Experiment", + "ScoreSubjectV3_Observation", + "ScoreSubjectV3_Session", + "ScoreSubjectV3_Trace", + "ScoreV3", + "ScoreV3_Boolean", + "ScoreV3_Categorical", + "ScoreV3_Correction", + "ScoreV3_Numeric", + "ScoreV3_Text", + "TextScoreV3", +] diff --git a/langfuse/api/scores_v3/types/base_score_v3.py b/langfuse/api/scores_v3/types/base_score_v3.py new file mode 100644 index 000000000..3d5394f95 --- /dev/null +++ b/langfuse/api/scores_v3/types/base_score_v3.py @@ -0,0 +1,71 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.score_source import ScoreSource +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .score_subject_v3 import ScoreSubjectV3 + + +class BaseScoreV3(UniversalBaseModel): + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + name: str + source: ScoreSource + timestamp: dt.datetime + environment: str = pydantic.Field() + """ + The environment from which this score originated. + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Optional comment attached to the score. Present when "details" is included in the fields parameter. + """ + + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = pydantic.Field(default=None) + """ + The score config ID, if this score was created from a config. Present when "details" is included in the fields parameter. + """ + + metadata: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field( + default=None + ) + """ + Arbitrary metadata attached to the score. Present when "details" is included in the fields parameter. + """ + + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = pydantic.Field(default=None) + """ + The user who created this score, if available. Present when "annotation" is included in the fields parameter. + """ + + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = pydantic.Field(default=None) + """ + The annotation queue this score belongs to, if any. Present when "annotation" is included in the fields parameter. + """ + + subject: typing.Optional[ScoreSubjectV3] = pydantic.Field(default=None) + """ + The entity this score is attached to (trace, observation, session, or experiment). Present when "subject" is included in the fields parameter. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/boolean_score_v3.py b/langfuse/api/scores_v3/types/boolean_score_v3.py new file mode 100644 index 000000000..5b94bc1d1 --- /dev/null +++ b/langfuse/api/scores_v3/types/boolean_score_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score_v3 import BaseScoreV3 + + +class BooleanScoreV3(BaseScoreV3): + value: bool = pydantic.Field() + """ + The boolean value of the score. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/categorical_score_v3.py b/langfuse/api/scores_v3/types/categorical_score_v3.py new file mode 100644 index 000000000..975b1f64c --- /dev/null +++ b/langfuse/api/scores_v3/types/categorical_score_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score_v3 import BaseScoreV3 + + +class CategoricalScoreV3(BaseScoreV3): + value: str = pydantic.Field() + """ + The string category value of the score. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/correction_score_v3.py b/langfuse/api/scores_v3/types/correction_score_v3.py new file mode 100644 index 000000000..1717a6e67 --- /dev/null +++ b/langfuse/api/scores_v3/types/correction_score_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score_v3 import BaseScoreV3 + + +class CorrectionScoreV3(BaseScoreV3): + value: str = pydantic.Field() + """ + The correction content of the score. Empty string if not set. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/get_scores_v3meta.py b/langfuse/api/scores_v3/types/get_scores_v3meta.py new file mode 100644 index 000000000..7dfcfe0e1 --- /dev/null +++ b/langfuse/api/scores_v3/types/get_scores_v3meta.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class GetScoresV3Meta(UniversalBaseModel): + limit: int + cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + URL-safe base64 (base64url) cursor for the next page. Absent when there are no more results. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/get_scores_v3response.py b/langfuse/api/scores_v3/types/get_scores_v3response.py new file mode 100644 index 000000000..4d625b29a --- /dev/null +++ b/langfuse/api/scores_v3/types/get_scores_v3response.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel +from .get_scores_v3meta import GetScoresV3Meta +from .score_v3 import ScoreV3 + + +class GetScoresV3Response(UniversalBaseModel): + data: typing.List[ScoreV3] + meta: GetScoresV3Meta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/numeric_score_v3.py b/langfuse/api/scores_v3/types/numeric_score_v3.py new file mode 100644 index 000000000..10df001a4 --- /dev/null +++ b/langfuse/api/scores_v3/types/numeric_score_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score_v3 import BaseScoreV3 + + +class NumericScoreV3(BaseScoreV3): + value: float = pydantic.Field() + """ + The numeric value of the score. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/score_subject_experiment_v3.py b/langfuse/api/scores_v3/types/score_subject_experiment_v3.py new file mode 100644 index 000000000..a71a49241 --- /dev/null +++ b/langfuse/api/scores_v3/types/score_subject_experiment_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ScoreSubjectExperimentV3(UniversalBaseModel): + id: str = pydantic.Field() + """ + The dataset run ID (experiment ID). + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/score_subject_observation_v3.py b/langfuse/api/scores_v3/types/score_subject_observation_v3.py new file mode 100644 index 000000000..1bc2edf20 --- /dev/null +++ b/langfuse/api/scores_v3/types/score_subject_observation_v3.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class ScoreSubjectObservationV3(UniversalBaseModel): + id: str = pydantic.Field() + """ + The observation ID. + """ + + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = pydantic.Field(default=None) + """ + The parent trace ID, if available. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/score_subject_session_v3.py b/langfuse/api/scores_v3/types/score_subject_session_v3.py new file mode 100644 index 000000000..cb9347583 --- /dev/null +++ b/langfuse/api/scores_v3/types/score_subject_session_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ScoreSubjectSessionV3(UniversalBaseModel): + id: str = pydantic.Field() + """ + The session ID. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/score_subject_trace_v3.py b/langfuse/api/scores_v3/types/score_subject_trace_v3.py new file mode 100644 index 000000000..26aab7f07 --- /dev/null +++ b/langfuse/api/scores_v3/types/score_subject_trace_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import UniversalBaseModel + + +class ScoreSubjectTraceV3(UniversalBaseModel): + id: str = pydantic.Field() + """ + The trace ID. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/scores_v3/types/score_subject_v3.py b/langfuse/api/scores_v3/types/score_subject_v3.py new file mode 100644 index 000000000..7464fda55 --- /dev/null +++ b/langfuse/api/scores_v3/types/score_subject_v3.py @@ -0,0 +1,76 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class ScoreSubjectV3_Trace(UniversalBaseModel): + """ + A reference to the entity this score is attached to. Discriminated by "kind" — one of trace, observation, session, or experiment. + """ + + kind: typing.Literal["trace"] = "trace" + id: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreSubjectV3_Observation(UniversalBaseModel): + """ + A reference to the entity this score is attached to. Discriminated by "kind" — one of trace, observation, session, or experiment. + """ + + kind: typing.Literal["observation"] = "observation" + id: str + trace_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="traceId") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreSubjectV3_Session(UniversalBaseModel): + """ + A reference to the entity this score is attached to. Discriminated by "kind" — one of trace, observation, session, or experiment. + """ + + kind: typing.Literal["session"] = "session" + id: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreSubjectV3_Experiment(UniversalBaseModel): + """ + A reference to the entity this score is attached to. Discriminated by "kind" — one of trace, observation, session, or experiment. + """ + + kind: typing.Literal["experiment"] = "experiment" + id: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +ScoreSubjectV3 = typing_extensions.Annotated[ + typing.Union[ + ScoreSubjectV3_Trace, + ScoreSubjectV3_Observation, + ScoreSubjectV3_Session, + ScoreSubjectV3_Experiment, + ], + pydantic.Field(discriminator="kind"), +] diff --git a/langfuse/api/scores_v3/types/score_v3.py b/langfuse/api/scores_v3/types/score_v3.py new file mode 100644 index 000000000..9921d1bda --- /dev/null +++ b/langfuse/api/scores_v3/types/score_v3.py @@ -0,0 +1,200 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ...commons.types.score_source import ScoreSource +from ...core.pydantic_utilities import UniversalBaseModel +from ...core.serialization import FieldMetadata +from .score_subject_v3 import ScoreSubjectV3 + + +class ScoreV3_Numeric(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["NUMERIC"], FieldMetadata(alias="dataType") + ] = "NUMERIC" + value: float + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + name: str + source: ScoreSource + timestamp: dt.datetime + environment: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + comment: typing.Optional[str] = None + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + subject: typing.Optional[ScoreSubjectV3] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreV3_Boolean(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["BOOLEAN"], FieldMetadata(alias="dataType") + ] = "BOOLEAN" + value: bool + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + name: str + source: ScoreSource + timestamp: dt.datetime + environment: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + comment: typing.Optional[str] = None + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + subject: typing.Optional[ScoreSubjectV3] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreV3_Categorical(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CATEGORICAL"], FieldMetadata(alias="dataType") + ] = "CATEGORICAL" + value: str + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + name: str + source: ScoreSource + timestamp: dt.datetime + environment: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + comment: typing.Optional[str] = None + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + subject: typing.Optional[ScoreSubjectV3] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreV3_Text(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["TEXT"], FieldMetadata(alias="dataType") + ] = "TEXT" + value: str + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + name: str + source: ScoreSource + timestamp: dt.datetime + environment: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + comment: typing.Optional[str] = None + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + subject: typing.Optional[ScoreSubjectV3] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class ScoreV3_Correction(UniversalBaseModel): + data_type: typing_extensions.Annotated[ + typing.Literal["CORRECTION"], FieldMetadata(alias="dataType") + ] = "CORRECTION" + value: str + id: str + project_id: typing_extensions.Annotated[str, FieldMetadata(alias="projectId")] + name: str + source: ScoreSource + timestamp: dt.datetime + environment: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + comment: typing.Optional[str] = None + config_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="configId") + ] = None + metadata: typing.Optional[typing.Dict[str, typing.Any]] = None + author_user_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="authorUserId") + ] = None + queue_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="queueId") + ] = None + subject: typing.Optional[ScoreSubjectV3] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +ScoreV3 = typing_extensions.Annotated[ + typing.Union[ + ScoreV3_Numeric, + ScoreV3_Boolean, + ScoreV3_Categorical, + ScoreV3_Text, + ScoreV3_Correction, + ], + pydantic.Field(discriminator="data_type"), +] diff --git a/langfuse/api/scores_v3/types/text_score_v3.py b/langfuse/api/scores_v3/types/text_score_v3.py new file mode 100644 index 000000000..3d658972c --- /dev/null +++ b/langfuse/api/scores_v3/types/text_score_v3.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from .base_score_v3 import BaseScoreV3 + + +class TextScoreV3(BaseScoreV3): + value: str = pydantic.Field() + """ + The text content of the score. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/__init__.py b/langfuse/api/unstable/__init__.py index 75aafdc24..e5356c235 100644 --- a/langfuse/api/unstable/__init__.py +++ b/langfuse/api/unstable/__init__.py @@ -26,6 +26,7 @@ ArrayOptionsEvaluationRuleFilter, BooleanEvaluationRuleFilter, CategoryOptionsEvaluationRuleFilter, + CodeEvaluatorSourceCodeLanguage, DateTimeEvaluationRuleFilter, EvaluationRuleArrayOptionsFilterOperator, EvaluationRuleBooleanFilterOperator, @@ -73,24 +74,51 @@ StringOptionsEvaluationRuleFilter, ) from .evaluation_rules import ( + CodeEvaluationRuleEvaluatorReference, + CreateCodeEvaluationRuleRequest, CreateEvaluationRuleRequest, + CreateLlmAsJudgeEvaluationRuleRequest, DeleteEvaluationRuleResponse, EvaluationRule, EvaluationRuleEvaluator, EvaluationRuleEvaluatorReference, EvaluationRules, + LlmAsJudgeEvaluationRuleEvaluatorReference, + LlmAsJudgeEvaluatorType, UpdateEvaluationRuleRequest, ) - from .evaluators import CreateEvaluatorRequest, Evaluator, Evaluators + from .evaluators import ( + CodeEvaluator, + CreateCodeEvaluatorRequest, + CreateEvaluatorRequest, + CreateEvaluatorRequest_Code, + CreateEvaluatorRequest_LlmAsJudge, + CreateLlmAsJudgeEvaluatorRequest, + Evaluator, + EvaluatorBase, + Evaluator_Code, + Evaluator_LlmAsJudge, + Evaluators, + LlmAsJudgeEvaluator, + ) _dynamic_imports: typing.Dict[str, str] = { "AccessDeniedError": ".errors", "ArrayOptionsEvaluationRuleFilter": ".commons", "BadRequestError": ".errors", "BooleanEvaluationRuleFilter": ".commons", "CategoryOptionsEvaluationRuleFilter": ".commons", + "CodeEvaluationRuleEvaluatorReference": ".evaluation_rules", + "CodeEvaluator": ".evaluators", + "CodeEvaluatorSourceCodeLanguage": ".commons", "ConflictError": ".errors", + "CreateCodeEvaluationRuleRequest": ".evaluation_rules", + "CreateCodeEvaluatorRequest": ".evaluators", "CreateEvaluationRuleRequest": ".evaluation_rules", "CreateEvaluatorRequest": ".evaluators", + "CreateEvaluatorRequest_Code": ".evaluators", + "CreateEvaluatorRequest_LlmAsJudge": ".evaluators", + "CreateLlmAsJudgeEvaluationRuleRequest": ".evaluation_rules", + "CreateLlmAsJudgeEvaluatorRequest": ".evaluators", "DateTimeEvaluationRuleFilter": ".commons", "DeleteEvaluationRuleResponse": ".evaluation_rules", "EvaluationRule": ".evaluation_rules", @@ -119,6 +147,7 @@ "EvaluationRuleTarget": ".commons", "EvaluationRules": ".evaluation_rules", "Evaluator": ".evaluators", + "EvaluatorBase": ".evaluators", "EvaluatorModelConfig": ".commons", "EvaluatorOutputDataType": ".commons", "EvaluatorOutputDefinition": ".commons", @@ -128,8 +157,13 @@ "EvaluatorOutputFieldDefinition": ".commons", "EvaluatorScope": ".commons", "EvaluatorType": ".commons", + "Evaluator_Code": ".evaluators", + "Evaluator_LlmAsJudge": ".evaluators", "Evaluators": ".evaluators", "InternalServerError": ".errors", + "LlmAsJudgeEvaluationRuleEvaluatorReference": ".evaluation_rules", + "LlmAsJudgeEvaluator": ".evaluators", + "LlmAsJudgeEvaluatorType": ".evaluation_rules", "MethodNotAllowedError": ".errors", "NotFoundError": ".errors", "NullEvaluationRuleFilter": ".commons", @@ -194,9 +228,18 @@ def __dir__(): "BadRequestError", "BooleanEvaluationRuleFilter", "CategoryOptionsEvaluationRuleFilter", + "CodeEvaluationRuleEvaluatorReference", + "CodeEvaluator", + "CodeEvaluatorSourceCodeLanguage", "ConflictError", + "CreateCodeEvaluationRuleRequest", + "CreateCodeEvaluatorRequest", "CreateEvaluationRuleRequest", "CreateEvaluatorRequest", + "CreateEvaluatorRequest_Code", + "CreateEvaluatorRequest_LlmAsJudge", + "CreateLlmAsJudgeEvaluationRuleRequest", + "CreateLlmAsJudgeEvaluatorRequest", "DateTimeEvaluationRuleFilter", "DeleteEvaluationRuleResponse", "EvaluationRule", @@ -225,6 +268,7 @@ def __dir__(): "EvaluationRuleTarget", "EvaluationRules", "Evaluator", + "EvaluatorBase", "EvaluatorModelConfig", "EvaluatorOutputDataType", "EvaluatorOutputDefinition", @@ -234,8 +278,13 @@ def __dir__(): "EvaluatorOutputFieldDefinition", "EvaluatorScope", "EvaluatorType", + "Evaluator_Code", + "Evaluator_LlmAsJudge", "Evaluators", "InternalServerError", + "LlmAsJudgeEvaluationRuleEvaluatorReference", + "LlmAsJudgeEvaluator", + "LlmAsJudgeEvaluatorType", "MethodNotAllowedError", "NotFoundError", "NullEvaluationRuleFilter", diff --git a/langfuse/api/unstable/commons/__init__.py b/langfuse/api/unstable/commons/__init__.py index 13d9571ff..c617b53c7 100644 --- a/langfuse/api/unstable/commons/__init__.py +++ b/langfuse/api/unstable/commons/__init__.py @@ -10,6 +10,7 @@ ArrayOptionsEvaluationRuleFilter, BooleanEvaluationRuleFilter, CategoryOptionsEvaluationRuleFilter, + CodeEvaluatorSourceCodeLanguage, DateTimeEvaluationRuleFilter, EvaluationRuleArrayOptionsFilterOperator, EvaluationRuleBooleanFilterOperator, @@ -60,6 +61,7 @@ "ArrayOptionsEvaluationRuleFilter": ".types", "BooleanEvaluationRuleFilter": ".types", "CategoryOptionsEvaluationRuleFilter": ".types", + "CodeEvaluatorSourceCodeLanguage": ".types", "DateTimeEvaluationRuleFilter": ".types", "EvaluationRuleArrayOptionsFilterOperator": ".types", "EvaluationRuleBooleanFilterOperator": ".types", @@ -139,6 +141,7 @@ def __dir__(): "ArrayOptionsEvaluationRuleFilter", "BooleanEvaluationRuleFilter", "CategoryOptionsEvaluationRuleFilter", + "CodeEvaluatorSourceCodeLanguage", "DateTimeEvaluationRuleFilter", "EvaluationRuleArrayOptionsFilterOperator", "EvaluationRuleBooleanFilterOperator", diff --git a/langfuse/api/unstable/commons/types/__init__.py b/langfuse/api/unstable/commons/types/__init__.py index a0e7d9f9d..487480da4 100644 --- a/langfuse/api/unstable/commons/types/__init__.py +++ b/langfuse/api/unstable/commons/types/__init__.py @@ -11,6 +11,7 @@ from .category_options_evaluation_rule_filter import ( CategoryOptionsEvaluationRuleFilter, ) + from .code_evaluator_source_code_language import CodeEvaluatorSourceCodeLanguage from .date_time_evaluation_rule_filter import DateTimeEvaluationRuleFilter from .evaluation_rule_array_options_filter_operator import ( EvaluationRuleArrayOptionsFilterOperator, @@ -84,6 +85,7 @@ "ArrayOptionsEvaluationRuleFilter": ".array_options_evaluation_rule_filter", "BooleanEvaluationRuleFilter": ".boolean_evaluation_rule_filter", "CategoryOptionsEvaluationRuleFilter": ".category_options_evaluation_rule_filter", + "CodeEvaluatorSourceCodeLanguage": ".code_evaluator_source_code_language", "DateTimeEvaluationRuleFilter": ".date_time_evaluation_rule_filter", "EvaluationRuleArrayOptionsFilterOperator": ".evaluation_rule_array_options_filter_operator", "EvaluationRuleBooleanFilterOperator": ".evaluation_rule_boolean_filter_operator", @@ -163,6 +165,7 @@ def __dir__(): "ArrayOptionsEvaluationRuleFilter", "BooleanEvaluationRuleFilter", "CategoryOptionsEvaluationRuleFilter", + "CodeEvaluatorSourceCodeLanguage", "DateTimeEvaluationRuleFilter", "EvaluationRuleArrayOptionsFilterOperator", "EvaluationRuleBooleanFilterOperator", diff --git a/langfuse/api/unstable/commons/types/code_evaluator_source_code_language.py b/langfuse/api/unstable/commons/types/code_evaluator_source_code_language.py new file mode 100644 index 000000000..7071a317c --- /dev/null +++ b/langfuse/api/unstable/commons/types/code_evaluator_source_code_language.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class CodeEvaluatorSourceCodeLanguage(enum.StrEnum): + """ + Code evaluator runtime language. + """ + + PYTHON = "PYTHON" + TYPESCRIPT = "TYPESCRIPT" + + def visit( + self, + python: typing.Callable[[], T_Result], + typescript: typing.Callable[[], T_Result], + ) -> T_Result: + if self is CodeEvaluatorSourceCodeLanguage.PYTHON: + return python() + if self is CodeEvaluatorSourceCodeLanguage.TYPESCRIPT: + return typescript() diff --git a/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py b/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py index 1c407819c..cd08e8b33 100644 --- a/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py +++ b/langfuse/api/unstable/commons/types/evaluation_rule_mapping.py @@ -11,7 +11,9 @@ class EvaluationRuleMapping(UniversalBaseModel): """ - Maps one evaluator prompt variable to one source field from the target object. + Maps one evaluator variable to one source field from the target object. + + Manual mappings are used for `llm_as_judge` evaluators. `code` evaluators use a fixed runtime mapping managed by Langfuse. How to build a valid mapping list: 1. Create the evaluator or fetch it with `GET /evaluators/{id}`. @@ -24,7 +26,7 @@ class EvaluationRuleMapping(UniversalBaseModel): Recovery guidance: - `invalid_variable_mapping`: the variable name is unknown for this evaluator, or the selected `source` is not valid for the chosen `target` - - `missing_variable_mapping`: one or more evaluator variables are not mapped yet + - `missing_variable_mapping`: one or more LLM-as-judge evaluator variables are not mapped yet - `duplicate_variable_mapping`: the same evaluator variable appears more than once - `invalid_json_path`: the JSONPath expression is malformed. Remove it or correct it. diff --git a/langfuse/api/unstable/commons/types/evaluator_type.py b/langfuse/api/unstable/commons/types/evaluator_type.py index d411d6111..f219fb7e1 100644 --- a/langfuse/api/unstable/commons/types/evaluator_type.py +++ b/langfuse/api/unstable/commons/types/evaluator_type.py @@ -11,11 +11,18 @@ class EvaluatorType(enum.StrEnum): """ The evaluator engine type. - The unstable public API currently supports only LLM-as-a-judge evaluators. + The unstable public API supports LLM-as-a-judge and code evaluators. """ LLM_AS_JUDGE = "llm_as_judge" + CODE = "code" - def visit(self, llm_as_judge: typing.Callable[[], T_Result]) -> T_Result: + def visit( + self, + llm_as_judge: typing.Callable[[], T_Result], + code: typing.Callable[[], T_Result], + ) -> T_Result: if self is EvaluatorType.LLM_AS_JUDGE: return llm_as_judge() + if self is EvaluatorType.CODE: + return code() diff --git a/langfuse/api/unstable/evaluation_rules/__init__.py b/langfuse/api/unstable/evaluation_rules/__init__.py index f0c007231..8541bdcc8 100644 --- a/langfuse/api/unstable/evaluation_rules/__init__.py +++ b/langfuse/api/unstable/evaluation_rules/__init__.py @@ -7,21 +7,31 @@ if typing.TYPE_CHECKING: from .types import ( + CodeEvaluationRuleEvaluatorReference, + CreateCodeEvaluationRuleRequest, CreateEvaluationRuleRequest, + CreateLlmAsJudgeEvaluationRuleRequest, DeleteEvaluationRuleResponse, EvaluationRule, EvaluationRuleEvaluator, EvaluationRuleEvaluatorReference, EvaluationRules, + LlmAsJudgeEvaluationRuleEvaluatorReference, + LlmAsJudgeEvaluatorType, UpdateEvaluationRuleRequest, ) _dynamic_imports: typing.Dict[str, str] = { + "CodeEvaluationRuleEvaluatorReference": ".types", + "CreateCodeEvaluationRuleRequest": ".types", "CreateEvaluationRuleRequest": ".types", + "CreateLlmAsJudgeEvaluationRuleRequest": ".types", "DeleteEvaluationRuleResponse": ".types", "EvaluationRule": ".types", "EvaluationRuleEvaluator": ".types", "EvaluationRuleEvaluatorReference": ".types", "EvaluationRules": ".types", + "LlmAsJudgeEvaluationRuleEvaluatorReference": ".types", + "LlmAsJudgeEvaluatorType": ".types", "UpdateEvaluationRuleRequest": ".types", } @@ -54,11 +64,16 @@ def __dir__(): __all__ = [ + "CodeEvaluationRuleEvaluatorReference", + "CreateCodeEvaluationRuleRequest", "CreateEvaluationRuleRequest", + "CreateLlmAsJudgeEvaluationRuleRequest", "DeleteEvaluationRuleResponse", "EvaluationRule", "EvaluationRuleEvaluator", "EvaluationRuleEvaluatorReference", "EvaluationRules", + "LlmAsJudgeEvaluationRuleEvaluatorReference", + "LlmAsJudgeEvaluatorType", "UpdateEvaluationRuleRequest", ] diff --git a/langfuse/api/unstable/evaluation_rules/client.py b/langfuse/api/unstable/evaluation_rules/client.py index 20e56e6c3..aa0cefbdf 100644 --- a/langfuse/api/unstable/evaluation_rules/client.py +++ b/langfuse/api/unstable/evaluation_rules/client.py @@ -8,6 +8,7 @@ from ..commons.types.evaluation_rule_mapping import EvaluationRuleMapping from ..commons.types.evaluation_rule_target import EvaluationRuleTarget from .raw_client import AsyncRawEvaluationRulesClient, RawEvaluationRulesClient +from .types.create_evaluation_rule_request import CreateEvaluationRuleRequest from .types.delete_evaluation_rule_response import DeleteEvaluationRuleResponse from .types.evaluation_rule import EvaluationRule from .types.evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference @@ -35,13 +36,7 @@ def with_raw_response(self) -> RawEvaluationRulesClient: def create( self, *, - name: str, - evaluator: EvaluationRuleEvaluatorReference, - target: EvaluationRuleTarget, - enabled: bool, - mapping: typing.Sequence[EvaluationRuleMapping], - sampling: typing.Optional[float] = OMIT, - filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request: CreateEvaluationRuleRequest, request_options: typing.Optional[RequestOptions] = None, ) -> EvaluationRule: """ @@ -57,8 +52,9 @@ def create( - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints - Langfuse resolves that family to its latest version before saving the evaluation rule - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` - - every evaluator prompt variable must be mapped exactly once - - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - for `llm_as_judge` evaluators, every evaluator prompt variable must be mapped exactly once + - for `code` evaluators, Langfuse uses the fixed code runtime mapping; omit `mapping` in create and update requests + - for user-provided `llm_as_judge` mappings, `expected_output` and `experiment_item_metadata` are only valid for `target=experiment` - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run - at most 50 evaluation rules can be effectively active in one project at the same time @@ -75,44 +71,15 @@ def create( Recovery guidance: - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response - - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 missing_variable_mapping`: for `llm_as_judge` evaluators, fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable - - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_variable_mapping`: for `llm_as_judge`, switch to a valid `source` for the selected `target`, or fix the variable name - `400 invalid_json_path`: remove or correct the `jsonPath` - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. Parameters ---------- - name : str - Human-readable deployment name. - - evaluator : EvaluationRuleEvaluatorReference - Evaluator family to use. - - Use `name` and `scope` from the evaluator endpoints. - Langfuse resolves that family to its latest version before saving the rule. - - target : EvaluationRuleTarget - Target object type to evaluate. - - enabled : bool - Whether the deployment should be active immediately after creation. - - mapping : typing.Sequence[EvaluationRuleMapping] - Required variable mappings. - - Every evaluator variable must appear exactly once. - Build this list from the evaluator `variables` array returned by the evaluator endpoints. - - sampling : typing.Optional[float] - Optional sampling fraction. Defaults to `1`. - - filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] - Optional filter list. - - Omit or pass an empty list to evaluate all matching targets for the selected `target`. - Each filter object must use a column that is valid for that `target`. - For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + request : CreateEvaluationRuleRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -132,7 +99,11 @@ def create( EvaluationRuleTarget, EvaluatorScope, ) - from langfuse.unstable.evaluation_rules import EvaluationRuleEvaluatorReference + from langfuse.unstable.evaluation_rules import ( + CreateLlmAsJudgeEvaluationRuleRequest, + LlmAsJudgeEvaluationRuleEvaluatorReference, + LlmAsJudgeEvaluatorType, + ) client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", @@ -143,42 +114,38 @@ def create( base_url="https://yourhost.com/path/to/api", ) client.unstable.evaluation_rules.create( - name="answer-correctness-live", - evaluator=EvaluationRuleEvaluatorReference( - name="answer-correctness", - scope=EvaluatorScope.PROJECT, - ), - target=EvaluationRuleTarget.OBSERVATION, - enabled=True, - sampling=1.0, - filter=[ - EvaluationRuleFilter_StringOptions( - column="type", - operator=EvaluationRuleOptionsFilterOperator.ANY_OF, - value=["GENERATION"], - ) - ], - mapping=[ - EvaluationRuleMapping( - variable="input", - source=EvaluationRuleMappingSource.INPUT, - ), - EvaluationRuleMapping( - variable="output", - source=EvaluationRuleMappingSource.OUTPUT, + request=CreateLlmAsJudgeEvaluationRuleRequest( + name="answer-correctness-live", + evaluator=LlmAsJudgeEvaluationRuleEvaluatorReference( + name="answer-correctness", + scope=EvaluatorScope.PROJECT, + type=LlmAsJudgeEvaluatorType.LLM_AS_JUDGE, ), - ], + target=EvaluationRuleTarget.OBSERVATION, + enabled=True, + sampling=1.0, + filter=[ + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + ], + mapping=[ + EvaluationRuleMapping( + variable="input", + source=EvaluationRuleMappingSource.INPUT, + ), + EvaluationRuleMapping( + variable="output", + source=EvaluationRuleMappingSource.OUTPUT, + ), + ], + ), ) """ _response = self._raw_client.create( - name=name, - evaluator=evaluator, - target=target, - enabled=enabled, - mapping=mapping, - sampling=sampling, - filter=filter, - request_options=request_options, + request=request, request_options=request_options ) return _response.data @@ -293,18 +260,19 @@ def update( - switch to another evaluator - adjust sampling - change filters - - update variable mappings + - update LLM-as-judge variable mappings Important behavior: - provide only the fields you want to change - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving - - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration - - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - changing `target`, `filter`, or an LLM-as-judge `mapping` must still produce a valid target-specific configuration + - if you change `target` for an LLM-as-judge rule, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - for `code` evaluator rules, omit `mapping`; Langfuse stores the fixed code runtime mapping automatically - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` Recovery guidance: - - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if an LLM-as-judge update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` Parameters @@ -319,6 +287,7 @@ def update( Updated evaluator family. Langfuse resolves the provided evaluator family to its latest version before saving the rule. + A rule's evaluator type cannot be changed: provide `name` and `scope` for an evaluator family of the rule's current type. To use a different evaluator type, create a new rule. target : typing.Optional[EvaluationRuleTarget] Updated target object type. @@ -335,7 +304,9 @@ def update( For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] - Updated variable mappings. + Updated LLM-as-judge variable mappings. + + Do not send this field for code evaluator rules. Langfuse stores the fixed code runtime mapping automatically and returns it in the response. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -436,13 +407,7 @@ def with_raw_response(self) -> AsyncRawEvaluationRulesClient: async def create( self, *, - name: str, - evaluator: EvaluationRuleEvaluatorReference, - target: EvaluationRuleTarget, - enabled: bool, - mapping: typing.Sequence[EvaluationRuleMapping], - sampling: typing.Optional[float] = OMIT, - filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request: CreateEvaluationRuleRequest, request_options: typing.Optional[RequestOptions] = None, ) -> EvaluationRule: """ @@ -458,8 +423,9 @@ async def create( - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints - Langfuse resolves that family to its latest version before saving the evaluation rule - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` - - every evaluator prompt variable must be mapped exactly once - - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - for `llm_as_judge` evaluators, every evaluator prompt variable must be mapped exactly once + - for `code` evaluators, Langfuse uses the fixed code runtime mapping; omit `mapping` in create and update requests + - for user-provided `llm_as_judge` mappings, `expected_output` and `experiment_item_metadata` are only valid for `target=experiment` - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run - at most 50 evaluation rules can be effectively active in one project at the same time @@ -476,44 +442,15 @@ async def create( Recovery guidance: - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response - - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 missing_variable_mapping`: for `llm_as_judge` evaluators, fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable - - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_variable_mapping`: for `llm_as_judge`, switch to a valid `source` for the selected `target`, or fix the variable name - `400 invalid_json_path`: remove or correct the `jsonPath` - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. Parameters ---------- - name : str - Human-readable deployment name. - - evaluator : EvaluationRuleEvaluatorReference - Evaluator family to use. - - Use `name` and `scope` from the evaluator endpoints. - Langfuse resolves that family to its latest version before saving the rule. - - target : EvaluationRuleTarget - Target object type to evaluate. - - enabled : bool - Whether the deployment should be active immediately after creation. - - mapping : typing.Sequence[EvaluationRuleMapping] - Required variable mappings. - - Every evaluator variable must appear exactly once. - Build this list from the evaluator `variables` array returned by the evaluator endpoints. - - sampling : typing.Optional[float] - Optional sampling fraction. Defaults to `1`. - - filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] - Optional filter list. - - Omit or pass an empty list to evaluate all matching targets for the selected `target`. - Each filter object must use a column that is valid for that `target`. - For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + request : CreateEvaluationRuleRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -535,7 +472,11 @@ async def create( EvaluationRuleTarget, EvaluatorScope, ) - from langfuse.unstable.evaluation_rules import EvaluationRuleEvaluatorReference + from langfuse.unstable.evaluation_rules import ( + CreateLlmAsJudgeEvaluationRuleRequest, + LlmAsJudgeEvaluationRuleEvaluatorReference, + LlmAsJudgeEvaluatorType, + ) client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", @@ -549,45 +490,41 @@ async def create( async def main() -> None: await client.unstable.evaluation_rules.create( - name="answer-correctness-live", - evaluator=EvaluationRuleEvaluatorReference( - name="answer-correctness", - scope=EvaluatorScope.PROJECT, - ), - target=EvaluationRuleTarget.OBSERVATION, - enabled=True, - sampling=1.0, - filter=[ - EvaluationRuleFilter_StringOptions( - column="type", - operator=EvaluationRuleOptionsFilterOperator.ANY_OF, - value=["GENERATION"], - ) - ], - mapping=[ - EvaluationRuleMapping( - variable="input", - source=EvaluationRuleMappingSource.INPUT, - ), - EvaluationRuleMapping( - variable="output", - source=EvaluationRuleMappingSource.OUTPUT, + request=CreateLlmAsJudgeEvaluationRuleRequest( + name="answer-correctness-live", + evaluator=LlmAsJudgeEvaluationRuleEvaluatorReference( + name="answer-correctness", + scope=EvaluatorScope.PROJECT, + type=LlmAsJudgeEvaluatorType.LLM_AS_JUDGE, ), - ], + target=EvaluationRuleTarget.OBSERVATION, + enabled=True, + sampling=1.0, + filter=[ + EvaluationRuleFilter_StringOptions( + column="type", + operator=EvaluationRuleOptionsFilterOperator.ANY_OF, + value=["GENERATION"], + ) + ], + mapping=[ + EvaluationRuleMapping( + variable="input", + source=EvaluationRuleMappingSource.INPUT, + ), + EvaluationRuleMapping( + variable="output", + source=EvaluationRuleMappingSource.OUTPUT, + ), + ], + ), ) asyncio.run(main()) """ _response = await self._raw_client.create( - name=name, - evaluator=evaluator, - target=target, - enabled=enabled, - mapping=mapping, - sampling=sampling, - filter=filter, - request_options=request_options, + request=request, request_options=request_options ) return _response.data @@ -718,18 +655,19 @@ async def update( - switch to another evaluator - adjust sampling - change filters - - update variable mappings + - update LLM-as-judge variable mappings Important behavior: - provide only the fields you want to change - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving - - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration - - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - changing `target`, `filter`, or an LLM-as-judge `mapping` must still produce a valid target-specific configuration + - if you change `target` for an LLM-as-judge rule, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - for `code` evaluator rules, omit `mapping`; Langfuse stores the fixed code runtime mapping automatically - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` Recovery guidance: - - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if an LLM-as-judge update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` Parameters @@ -744,6 +682,7 @@ async def update( Updated evaluator family. Langfuse resolves the provided evaluator family to its latest version before saving the rule. + A rule's evaluator type cannot be changed: provide `name` and `scope` for an evaluator family of the rule's current type. To use a different evaluator type, create a new rule. target : typing.Optional[EvaluationRuleTarget] Updated target object type. @@ -760,7 +699,9 @@ async def update( For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] - Updated variable mappings. + Updated LLM-as-judge variable mappings. + + Do not send this field for code evaluator rules. Langfuse stores the fixed code runtime mapping automatically and returns it in the response. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/unstable/evaluation_rules/raw_client.py b/langfuse/api/unstable/evaluation_rules/raw_client.py index f99aba663..7115cbe70 100644 --- a/langfuse/api/unstable/evaluation_rules/raw_client.py +++ b/langfuse/api/unstable/evaluation_rules/raw_client.py @@ -44,6 +44,7 @@ ) from ..errors.errors.unprocessable_content_error import UnprocessableContentError from ..errors.types.public_api_error import PublicApiError +from .types.create_evaluation_rule_request import CreateEvaluationRuleRequest from .types.delete_evaluation_rule_response import DeleteEvaluationRuleResponse from .types.evaluation_rule import EvaluationRule from .types.evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference @@ -60,13 +61,7 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def create( self, *, - name: str, - evaluator: EvaluationRuleEvaluatorReference, - target: EvaluationRuleTarget, - enabled: bool, - mapping: typing.Sequence[EvaluationRuleMapping], - sampling: typing.Optional[float] = OMIT, - filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request: CreateEvaluationRuleRequest, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[EvaluationRule]: """ @@ -82,8 +77,9 @@ def create( - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints - Langfuse resolves that family to its latest version before saving the evaluation rule - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` - - every evaluator prompt variable must be mapped exactly once - - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - for `llm_as_judge` evaluators, every evaluator prompt variable must be mapped exactly once + - for `code` evaluators, Langfuse uses the fixed code runtime mapping; omit `mapping` in create and update requests + - for user-provided `llm_as_judge` mappings, `expected_output` and `experiment_item_metadata` are only valid for `target=experiment` - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run - at most 50 evaluation rules can be effectively active in one project at the same time @@ -100,44 +96,15 @@ def create( Recovery guidance: - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response - - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 missing_variable_mapping`: for `llm_as_judge` evaluators, fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable - - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_variable_mapping`: for `llm_as_judge`, switch to a valid `source` for the selected `target`, or fix the variable name - `400 invalid_json_path`: remove or correct the `jsonPath` - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. Parameters ---------- - name : str - Human-readable deployment name. - - evaluator : EvaluationRuleEvaluatorReference - Evaluator family to use. - - Use `name` and `scope` from the evaluator endpoints. - Langfuse resolves that family to its latest version before saving the rule. - - target : EvaluationRuleTarget - Target object type to evaluate. - - enabled : bool - Whether the deployment should be active immediately after creation. - - mapping : typing.Sequence[EvaluationRuleMapping] - Required variable mappings. - - Every evaluator variable must appear exactly once. - Build this list from the evaluator `variables` array returned by the evaluator endpoints. - - sampling : typing.Optional[float] - Optional sampling fraction. Defaults to `1`. - - filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] - Optional filter list. - - Omit or pass an empty list to evaluate all matching targets for the selected `target`. - Each filter object must use a column that is valid for that `target`. - For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + request : CreateEvaluationRuleRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -149,27 +116,11 @@ def create( _response = self._client_wrapper.httpx_client.request( "api/public/unstable/evaluation-rules", method="POST", - json={ - "name": name, - "evaluator": convert_and_respect_annotation_metadata( - object_=evaluator, - annotation=EvaluationRuleEvaluatorReference, - direction="write", - ), - "target": target, - "enabled": enabled, - "sampling": sampling, - "filter": convert_and_respect_annotation_metadata( - object_=filter, - annotation=typing.Sequence[EvaluationRuleFilter], - direction="write", - ), - "mapping": convert_and_respect_annotation_metadata( - object_=mapping, - annotation=typing.Sequence[EvaluationRuleMapping], - direction="write", - ), - }, + json=convert_and_respect_annotation_metadata( + object_=request, + annotation=CreateEvaluationRuleRequest, + direction="write", + ), request_options=request_options, omit=OMIT, ) @@ -734,18 +685,19 @@ def update( - switch to another evaluator - adjust sampling - change filters - - update variable mappings + - update LLM-as-judge variable mappings Important behavior: - provide only the fields you want to change - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving - - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration - - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - changing `target`, `filter`, or an LLM-as-judge `mapping` must still produce a valid target-specific configuration + - if you change `target` for an LLM-as-judge rule, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - for `code` evaluator rules, omit `mapping`; Langfuse stores the fixed code runtime mapping automatically - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` Recovery guidance: - - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if an LLM-as-judge update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` Parameters @@ -760,6 +712,7 @@ def update( Updated evaluator family. Langfuse resolves the provided evaluator family to its latest version before saving the rule. + A rule's evaluator type cannot be changed: provide `name` and `scope` for an evaluator family of the rule's current type. To use a different evaluator type, create a new rule. target : typing.Optional[EvaluationRuleTarget] Updated target object type. @@ -776,7 +729,9 @@ def update( For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] - Updated variable mappings. + Updated LLM-as-judge variable mappings. + + Do not send this field for code evaluator rules. Langfuse stores the fixed code runtime mapping automatically and returns it in the response. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1169,13 +1124,7 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def create( self, *, - name: str, - evaluator: EvaluationRuleEvaluatorReference, - target: EvaluationRuleTarget, - enabled: bool, - mapping: typing.Sequence[EvaluationRuleMapping], - sampling: typing.Optional[float] = OMIT, - filter: typing.Optional[typing.Sequence[EvaluationRuleFilter]] = OMIT, + request: CreateEvaluationRuleRequest, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[EvaluationRule]: """ @@ -1191,8 +1140,9 @@ async def create( - `evaluator.name` + `evaluator.scope` must identify an existing evaluator family returned by the evaluator endpoints - Langfuse resolves that family to its latest version before saving the evaluation rule - for `target=experiment`, use dataset `id` values from `GET /api/public/v2/datasets` when filtering by `datasetId` - - every evaluator prompt variable must be mapped exactly once - - `expected_output` and `experiment_item_metadata` mappings are only valid for `target=experiment` + - for `llm_as_judge` evaluators, every evaluator prompt variable must be mapped exactly once + - for `code` evaluators, Langfuse uses the fixed code runtime mapping; omit `mapping` in create and update requests + - for user-provided `llm_as_judge` mappings, `expected_output` and `experiment_item_metadata` are only valid for `target=experiment` - if `enabled=true`, Langfuse validates that the referenced evaluator can currently run - at most 50 evaluation rules can be effectively active in one project at the same time @@ -1209,44 +1159,15 @@ async def create( Recovery guidance: - `400 invalid_filter_value`: fix the filter `column` or `value` using `details.column`, `details.invalidValues`, and `details.allowedValues` - `400 invalid_filter_value` with `details.column=datasetId`: call `GET /api/public/v2/datasets`, then retry with dataset `id` values from that response - - `400 missing_variable_mapping`: fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` + - `400 missing_variable_mapping`: for `llm_as_judge` evaluators, fetch the evaluator again and make sure every variable in `variables` appears exactly once in `mapping` - `400 duplicate_variable_mapping`: remove repeated mappings for the same variable - - `400 invalid_variable_mapping`: switch to a valid `source` for the selected `target`, or fix the variable name + - `400 invalid_variable_mapping`: for `llm_as_judge`, switch to a valid `source` for the selected `target`, or fix the variable name - `400 invalid_json_path`: remove or correct the `jsonPath` - `422 evaluator_preflight_failed`: the selected evaluator cannot run with the resolved model configuration. Fix the evaluator/default model setup, then retry the create request. Parameters ---------- - name : str - Human-readable deployment name. - - evaluator : EvaluationRuleEvaluatorReference - Evaluator family to use. - - Use `name` and `scope` from the evaluator endpoints. - Langfuse resolves that family to its latest version before saving the rule. - - target : EvaluationRuleTarget - Target object type to evaluate. - - enabled : bool - Whether the deployment should be active immediately after creation. - - mapping : typing.Sequence[EvaluationRuleMapping] - Required variable mappings. - - Every evaluator variable must appear exactly once. - Build this list from the evaluator `variables` array returned by the evaluator endpoints. - - sampling : typing.Optional[float] - Optional sampling fraction. Defaults to `1`. - - filter : typing.Optional[typing.Sequence[EvaluationRuleFilter]] - Optional filter list. - - Omit or pass an empty list to evaluate all matching targets for the selected `target`. - Each filter object must use a column that is valid for that `target`. - For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + request : CreateEvaluationRuleRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1258,27 +1179,11 @@ async def create( _response = await self._client_wrapper.httpx_client.request( "api/public/unstable/evaluation-rules", method="POST", - json={ - "name": name, - "evaluator": convert_and_respect_annotation_metadata( - object_=evaluator, - annotation=EvaluationRuleEvaluatorReference, - direction="write", - ), - "target": target, - "enabled": enabled, - "sampling": sampling, - "filter": convert_and_respect_annotation_metadata( - object_=filter, - annotation=typing.Sequence[EvaluationRuleFilter], - direction="write", - ), - "mapping": convert_and_respect_annotation_metadata( - object_=mapping, - annotation=typing.Sequence[EvaluationRuleMapping], - direction="write", - ), - }, + json=convert_and_respect_annotation_metadata( + object_=request, + annotation=CreateEvaluationRuleRequest, + direction="write", + ), request_options=request_options, omit=OMIT, ) @@ -1843,18 +1748,19 @@ async def update( - switch to another evaluator - adjust sampling - change filters - - update variable mappings + - update LLM-as-judge variable mappings Important behavior: - provide only the fields you want to change - if you provide `evaluator`, Langfuse resolves that evaluator family to its latest version before saving - - changing `target`, `filter`, or `mapping` must still produce a valid target-specific configuration - - if you change `target`, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - changing `target`, `filter`, or an LLM-as-judge `mapping` must still produce a valid target-specific configuration + - if you change `target` for an LLM-as-judge rule, also send a compatible `filter` and `mapping` in the same request unless the existing ones are still valid for the new target + - for `code` evaluator rules, omit `mapping`; Langfuse stores the fixed code runtime mapping automatically - if the resulting config is enabled, Langfuse re-validates that the selected evaluator can run - if the update would move a non-active evaluation rule into the active state and the project already has 50 active evaluation rules, the API returns `409` Recovery guidance: - - if the update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` + - if an LLM-as-judge update fails with `missing_variable_mapping` or `invalid_variable_mapping` after changing `evaluator` or `target`, resend the request with a complete new `mapping` - if the update fails with `invalid_filter_value` after changing `target`, resend the request with a target-compatible `filter` Parameters @@ -1869,6 +1775,7 @@ async def update( Updated evaluator family. Langfuse resolves the provided evaluator family to its latest version before saving the rule. + A rule's evaluator type cannot be changed: provide `name` and `scope` for an evaluator family of the rule's current type. To use a different evaluator type, create a new rule. target : typing.Optional[EvaluationRuleTarget] Updated target object type. @@ -1885,7 +1792,9 @@ async def update( For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. mapping : typing.Optional[typing.Sequence[EvaluationRuleMapping]] - Updated variable mappings. + Updated LLM-as-judge variable mappings. + + Do not send this field for code evaluator rules. Langfuse stores the fixed code runtime mapping automatically and returns it in the response. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/unstable/evaluation_rules/types/__init__.py b/langfuse/api/unstable/evaluation_rules/types/__init__.py index 2854b1237..a1cdeb967 100644 --- a/langfuse/api/unstable/evaluation_rules/types/__init__.py +++ b/langfuse/api/unstable/evaluation_rules/types/__init__.py @@ -6,20 +6,36 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .code_evaluation_rule_evaluator_reference import ( + CodeEvaluationRuleEvaluatorReference, + ) + from .create_code_evaluation_rule_request import CreateCodeEvaluationRuleRequest from .create_evaluation_rule_request import CreateEvaluationRuleRequest + from .create_llm_as_judge_evaluation_rule_request import ( + CreateLlmAsJudgeEvaluationRuleRequest, + ) from .delete_evaluation_rule_response import DeleteEvaluationRuleResponse from .evaluation_rule import EvaluationRule from .evaluation_rule_evaluator import EvaluationRuleEvaluator from .evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference from .evaluation_rules import EvaluationRules + from .llm_as_judge_evaluation_rule_evaluator_reference import ( + LlmAsJudgeEvaluationRuleEvaluatorReference, + ) + from .llm_as_judge_evaluator_type import LlmAsJudgeEvaluatorType from .update_evaluation_rule_request import UpdateEvaluationRuleRequest _dynamic_imports: typing.Dict[str, str] = { + "CodeEvaluationRuleEvaluatorReference": ".code_evaluation_rule_evaluator_reference", + "CreateCodeEvaluationRuleRequest": ".create_code_evaluation_rule_request", "CreateEvaluationRuleRequest": ".create_evaluation_rule_request", + "CreateLlmAsJudgeEvaluationRuleRequest": ".create_llm_as_judge_evaluation_rule_request", "DeleteEvaluationRuleResponse": ".delete_evaluation_rule_response", "EvaluationRule": ".evaluation_rule", "EvaluationRuleEvaluator": ".evaluation_rule_evaluator", "EvaluationRuleEvaluatorReference": ".evaluation_rule_evaluator_reference", "EvaluationRules": ".evaluation_rules", + "LlmAsJudgeEvaluationRuleEvaluatorReference": ".llm_as_judge_evaluation_rule_evaluator_reference", + "LlmAsJudgeEvaluatorType": ".llm_as_judge_evaluator_type", "UpdateEvaluationRuleRequest": ".update_evaluation_rule_request", } @@ -52,11 +68,16 @@ def __dir__(): __all__ = [ + "CodeEvaluationRuleEvaluatorReference", + "CreateCodeEvaluationRuleRequest", "CreateEvaluationRuleRequest", + "CreateLlmAsJudgeEvaluationRuleRequest", "DeleteEvaluationRuleResponse", "EvaluationRule", "EvaluationRuleEvaluator", "EvaluationRuleEvaluatorReference", "EvaluationRules", + "LlmAsJudgeEvaluationRuleEvaluatorReference", + "LlmAsJudgeEvaluatorType", "UpdateEvaluationRuleRequest", ] diff --git a/langfuse/api/unstable/evaluation_rules/types/code_evaluation_rule_evaluator_reference.py b/langfuse/api/unstable/evaluation_rules/types/code_evaluation_rule_evaluator_reference.py new file mode 100644 index 000000000..1c259bab8 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/code_evaluation_rule_evaluator_reference.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluator_scope import EvaluatorScope + + +class CodeEvaluationRuleEvaluatorReference(UniversalBaseModel): + """ + Code evaluator family reference used when creating an evaluation rule. + """ + + name: str = pydantic.Field() + """ + Evaluator family name. + """ + + scope: EvaluatorScope = pydantic.Field() + """ + Whether the evaluator family is project-owned or Langfuse-managed. + """ + + type: typing.Literal["code"] = pydantic.Field(default="code") + """ + Must be `code`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/create_code_evaluation_rule_request.py b/langfuse/api/unstable/evaluation_rules/types/create_code_evaluation_rule_request.py new file mode 100644 index 000000000..08df1f78a --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/create_code_evaluation_rule_request.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ...commons.types.evaluation_rule_target import EvaluationRuleTarget +from .code_evaluation_rule_evaluator_reference import ( + CodeEvaluationRuleEvaluatorReference, +) + + +class CreateCodeEvaluationRuleRequest(UniversalBaseModel): + name: str = pydantic.Field() + """ + Human-readable deployment name. + """ + + evaluator: CodeEvaluationRuleEvaluatorReference = pydantic.Field() + """ + Code evaluator family to use. + + Use `name`, `scope`, and `type` from the evaluator endpoints. + Langfuse resolves that family to its latest version before saving the rule. + """ + + target: EvaluationRuleTarget = pydantic.Field() + """ + Target object type to evaluate. + """ + + enabled: bool = pydantic.Field() + """ + Whether the deployment should be active immediately after creation. + """ + + sampling: typing.Optional[float] = pydantic.Field(default=None) + """ + Optional sampling fraction. Defaults to `1`. + """ + + filter: typing.Optional[typing.List[EvaluationRuleFilter]] = pydantic.Field( + default=None + ) + """ + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py b/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py index 9a90b227a..a6504934d 100644 --- a/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py +++ b/langfuse/api/unstable/evaluation_rules/types/create_evaluation_rule_request.py @@ -2,74 +2,11 @@ import typing -import pydantic -from ....core.pydantic_utilities import UniversalBaseModel -from ...commons.types.evaluation_rule_filter import EvaluationRuleFilter -from ...commons.types.evaluation_rule_mapping import EvaluationRuleMapping -from ...commons.types.evaluation_rule_target import EvaluationRuleTarget -from .evaluation_rule_evaluator_reference import EvaluationRuleEvaluatorReference - - -class CreateEvaluationRuleRequest(UniversalBaseModel): - """ - Request body for creating an evaluation rule. - - Checklist for agents and SDK clients: - - reference an existing evaluator family by `evaluator.name` and `evaluator.scope` - - choose `target=observation` or `target=experiment` - - if `target=experiment` and you want a dataset filter, call `GET /api/public/v2/datasets` first and use dataset `id` values in `filter[].value` - - fetch or inspect the evaluator first, then provide a complete variable mapping for every evaluator variable listed in `variables` - - optionally narrow execution with `filter` - - set `enabled=true` only when you want live execution immediately - """ - - name: str = pydantic.Field() - """ - Human-readable deployment name. - """ - - evaluator: EvaluationRuleEvaluatorReference = pydantic.Field() - """ - Evaluator family to use. - - Use `name` and `scope` from the evaluator endpoints. - Langfuse resolves that family to its latest version before saving the rule. - """ - - target: EvaluationRuleTarget = pydantic.Field() - """ - Target object type to evaluate. - """ - - enabled: bool = pydantic.Field() - """ - Whether the deployment should be active immediately after creation. - """ - - sampling: typing.Optional[float] = pydantic.Field(default=None) - """ - Optional sampling fraction. Defaults to `1`. - """ - - filter: typing.Optional[typing.List[EvaluationRuleFilter]] = pydantic.Field( - default=None - ) - """ - Optional filter list. - - Omit or pass an empty list to evaluate all matching targets for the selected `target`. - Each filter object must use a column that is valid for that `target`. - For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. - """ - - mapping: typing.List[EvaluationRuleMapping] = pydantic.Field() - """ - Required variable mappings. - - Every evaluator variable must appear exactly once. - Build this list from the evaluator `variables` array returned by the evaluator endpoints. - """ - - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( - extra="allow", frozen=True - ) +from .create_code_evaluation_rule_request import CreateCodeEvaluationRuleRequest +from .create_llm_as_judge_evaluation_rule_request import ( + CreateLlmAsJudgeEvaluationRuleRequest, +) + +CreateEvaluationRuleRequest = typing.Union[ + CreateLlmAsJudgeEvaluationRuleRequest, CreateCodeEvaluationRuleRequest +] diff --git a/langfuse/api/unstable/evaluation_rules/types/create_llm_as_judge_evaluation_rule_request.py b/langfuse/api/unstable/evaluation_rules/types/create_llm_as_judge_evaluation_rule_request.py new file mode 100644 index 000000000..b511b4353 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/create_llm_as_judge_evaluation_rule_request.py @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluation_rule_filter import EvaluationRuleFilter +from ...commons.types.evaluation_rule_mapping import EvaluationRuleMapping +from ...commons.types.evaluation_rule_target import EvaluationRuleTarget +from .llm_as_judge_evaluation_rule_evaluator_reference import ( + LlmAsJudgeEvaluationRuleEvaluatorReference, +) + + +class CreateLlmAsJudgeEvaluationRuleRequest(UniversalBaseModel): + name: str = pydantic.Field() + """ + Human-readable deployment name. + """ + + evaluator: LlmAsJudgeEvaluationRuleEvaluatorReference = pydantic.Field() + """ + LLM-as-judge evaluator family to use. + + Use `name`, `scope`, and `type` from the evaluator endpoints. If `type` is omitted, Langfuse defaults it to `llm_as_judge` for backwards compatibility. + Langfuse resolves that family to its latest version before saving the rule. + """ + + target: EvaluationRuleTarget = pydantic.Field() + """ + Target object type to evaluate. + """ + + enabled: bool = pydantic.Field() + """ + Whether the deployment should be active immediately after creation. + """ + + sampling: typing.Optional[float] = pydantic.Field(default=None) + """ + Optional sampling fraction. Defaults to `1`. + """ + + filter: typing.Optional[typing.List[EvaluationRuleFilter]] = pydantic.Field( + default=None + ) + """ + Optional filter list. + + Omit or pass an empty list to evaluate all matching targets for the selected `target`. + Each filter object must use a column that is valid for that `target`. + For `target=experiment`, `column=datasetId` expects dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + """ + + mapping: typing.List[EvaluationRuleMapping] = pydantic.Field() + """ + LLM-as-judge variable mappings. + + Every evaluator variable must appear exactly once. + Build this list from the evaluator `variables` array returned by the evaluator endpoints. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py index d8baee407..418004090 100644 --- a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule.py @@ -42,6 +42,7 @@ class EvaluationRule(UniversalBaseModel): EvaluationRuleStatus, EvaluationRuleTarget, EvaluatorScope, + EvaluatorType, ) from langfuse.unstable.evaluation_rules import ( EvaluationRule, @@ -55,6 +56,7 @@ class EvaluationRule(UniversalBaseModel): id="evaltmpl_123", name="answer-correctness", scope=EvaluatorScope.PROJECT, + type=EvaluatorType.LLM_AS_JUDGE, ), target=EvaluationRuleTarget.OBSERVATION, enabled=True, @@ -150,7 +152,7 @@ class EvaluationRule(UniversalBaseModel): mapping: typing.List[EvaluationRuleMapping] = pydantic.Field() """ - Variable mappings used to populate the evaluator prompt from the live target object. + Variable mappings used to populate evaluator runtime variables from the live target object. """ created_at: typing_extensions.Annotated[ diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py index 9d1be79de..c27497c9d 100644 --- a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator.py @@ -5,6 +5,7 @@ import pydantic from ....core.pydantic_utilities import UniversalBaseModel from ...commons.types.evaluator_scope import EvaluatorScope +from ...commons.types.evaluator_type import EvaluatorType class EvaluationRuleEvaluator(UniversalBaseModel): @@ -12,7 +13,7 @@ class EvaluationRuleEvaluator(UniversalBaseModel): Resolved evaluator currently used by the evaluation rule. `id` is the exact active evaluator version. - `name` and `scope` identify the evaluator family conceptually. + `name`, `scope`, and `type` identify the evaluator family conceptually. """ id: str = pydantic.Field() @@ -30,6 +31,11 @@ class EvaluationRuleEvaluator(UniversalBaseModel): Whether the evaluator family is project-owned or Langfuse-managed. """ + type: EvaluatorType = pydantic.Field() + """ + Evaluator engine type. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) diff --git a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py index 25253182f..a2a38723d 100644 --- a/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py +++ b/langfuse/api/unstable/evaluation_rules/types/evaluation_rule_evaluator_reference.py @@ -9,9 +9,10 @@ class EvaluationRuleEvaluatorReference(UniversalBaseModel): """ - Evaluator family reference used when creating or updating an evaluation rule. + Evaluator family reference used when updating an evaluation rule. - `name` and `scope` are enough to identify the evaluator family in the authenticated project context. + `name` and `scope` identify the evaluator family in the authenticated project context. + A rule's evaluator type cannot be changed, so this reference does not accept a `type`; the family must match the rule's current evaluator type. """ name: str = pydantic.Field() diff --git a/langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluation_rule_evaluator_reference.py b/langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluation_rule_evaluator_reference.py new file mode 100644 index 000000000..ca57fe517 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluation_rule_evaluator_reference.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ...commons.types.evaluator_scope import EvaluatorScope +from .llm_as_judge_evaluator_type import LlmAsJudgeEvaluatorType + + +class LlmAsJudgeEvaluationRuleEvaluatorReference(UniversalBaseModel): + """ + LLM-as-judge evaluator family reference used when creating an evaluation rule. + """ + + name: str = pydantic.Field() + """ + Evaluator family name. + """ + + scope: EvaluatorScope = pydantic.Field() + """ + Whether the evaluator family is project-owned or Langfuse-managed. + """ + + type: typing.Optional[LlmAsJudgeEvaluatorType] = pydantic.Field(default=None) + """ + Evaluator engine type. Defaults to `llm_as_judge` when omitted. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluator_type.py b/langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluator_type.py new file mode 100644 index 000000000..b18856d22 --- /dev/null +++ b/langfuse/api/unstable/evaluation_rules/types/llm_as_judge_evaluator_type.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class LlmAsJudgeEvaluatorType(enum.StrEnum): + LLM_AS_JUDGE = "llm_as_judge" + + def visit(self, llm_as_judge: typing.Callable[[], T_Result]) -> T_Result: + if self is LlmAsJudgeEvaluatorType.LLM_AS_JUDGE: + return llm_as_judge() diff --git a/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py b/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py index 51e2d9288..40e5043a6 100644 --- a/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py +++ b/langfuse/api/unstable/evaluation_rules/types/update_evaluation_rule_request.py @@ -19,8 +19,9 @@ class UpdateEvaluationRuleRequest(UniversalBaseModel): Practical guidance: - If you only want to rename the rule or change sampling, send just those fields. - - If you change `evaluator`, send a fresh `mapping` unless you are certain the existing mapping still matches the evaluator variables. - - If you change `target`, usually send both `filter` and `mapping` in the same request. + - If you change to an LLM-as-judge `evaluator`, send a fresh `mapping` unless you are certain the existing mapping still matches the evaluator variables. + - If you change `target` for an LLM-as-judge rule, usually send both `filter` and `mapping` in the same request. + - For code evaluator rules, omit `mapping`; Langfuse stores the fixed code runtime mapping automatically. - If you change an experiment `datasetId` filter, call `GET /api/public/v2/datasets` and use dataset `id` values from that response. """ @@ -36,6 +37,7 @@ class UpdateEvaluationRuleRequest(UniversalBaseModel): Updated evaluator family. Langfuse resolves the provided evaluator family to its latest version before saving the rule. + A rule's evaluator type cannot be changed: provide `name` and `scope` for an evaluator family of the rule's current type. To use a different evaluator type, create a new rule. """ target: typing.Optional[EvaluationRuleTarget] = pydantic.Field(default=None) @@ -66,7 +68,9 @@ class UpdateEvaluationRuleRequest(UniversalBaseModel): default=None ) """ - Updated variable mappings. + Updated LLM-as-judge variable mappings. + + Do not send this field for code evaluator rules. Langfuse stores the fixed code runtime mapping automatically and returns it in the response. """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( diff --git a/langfuse/api/unstable/evaluators/__init__.py b/langfuse/api/unstable/evaluators/__init__.py index 942109740..20a72ef82 100644 --- a/langfuse/api/unstable/evaluators/__init__.py +++ b/langfuse/api/unstable/evaluators/__init__.py @@ -6,11 +6,33 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import CreateEvaluatorRequest, Evaluator, Evaluators + from .types import ( + CodeEvaluator, + CreateCodeEvaluatorRequest, + CreateEvaluatorRequest, + CreateEvaluatorRequest_Code, + CreateEvaluatorRequest_LlmAsJudge, + CreateLlmAsJudgeEvaluatorRequest, + Evaluator, + EvaluatorBase, + Evaluator_Code, + Evaluator_LlmAsJudge, + Evaluators, + LlmAsJudgeEvaluator, + ) _dynamic_imports: typing.Dict[str, str] = { + "CodeEvaluator": ".types", + "CreateCodeEvaluatorRequest": ".types", "CreateEvaluatorRequest": ".types", + "CreateEvaluatorRequest_Code": ".types", + "CreateEvaluatorRequest_LlmAsJudge": ".types", + "CreateLlmAsJudgeEvaluatorRequest": ".types", "Evaluator": ".types", + "EvaluatorBase": ".types", + "Evaluator_Code": ".types", + "Evaluator_LlmAsJudge": ".types", "Evaluators": ".types", + "LlmAsJudgeEvaluator": ".types", } @@ -41,4 +63,17 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["CreateEvaluatorRequest", "Evaluator", "Evaluators"] +__all__ = [ + "CodeEvaluator", + "CreateCodeEvaluatorRequest", + "CreateEvaluatorRequest", + "CreateEvaluatorRequest_Code", + "CreateEvaluatorRequest_LlmAsJudge", + "CreateLlmAsJudgeEvaluatorRequest", + "Evaluator", + "EvaluatorBase", + "Evaluator_Code", + "Evaluator_LlmAsJudge", + "Evaluators", + "LlmAsJudgeEvaluator", +] diff --git a/langfuse/api/unstable/evaluators/client.py b/langfuse/api/unstable/evaluators/client.py index b7f25532a..ac63e2da9 100644 --- a/langfuse/api/unstable/evaluators/client.py +++ b/langfuse/api/unstable/evaluators/client.py @@ -4,9 +4,8 @@ from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ...core.request_options import RequestOptions -from ..commons.types.evaluator_model_config import EvaluatorModelConfig -from ..commons.types.evaluator_output_definition import EvaluatorOutputDefinition from .raw_client import AsyncRawEvaluatorsClient, RawEvaluatorsClient +from .types.create_evaluator_request import CreateEvaluatorRequest from .types.evaluator import Evaluator from .types.evaluators import Evaluators @@ -32,16 +31,15 @@ def with_raw_response(self) -> RawEvaluatorsClient: def create( self, *, - name: str, - prompt: str, - output_definition: EvaluatorOutputDefinition, - model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request: CreateEvaluatorRequest, request_options: typing.Optional[RequestOptions] = None, ) -> Evaluator: """ Create an evaluator in the authenticated project. - Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + Use evaluators to define **how** Langfuse should score data. + LLM-as-a-judge evaluators define a prompt, expected structured output, and optional model configuration. + Code evaluators define source code and a runtime language. Naming behavior: - If this is a new evaluator name in your project, Langfuse creates version `1`. @@ -54,30 +52,22 @@ def create( 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + Code evaluator validation: + - At creation, Langfuse only validates the request shape + - The `sourceCode` itself is not executed here. It is first run (preflight-tested against a sample observation) when you link the evaluator to an evaluation rule, so runtime errors in the code surface at evaluation-rule creation, not at evaluator creation. + Recovery guidance: - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. - - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - `400` with `code=invalid_body` on `outputDefinition`: for `type=llm_as_judge`, send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - If `type` is omitted, Langfuse treats the request as `type=llm_as_judge` for backwards compatibility. New clients should send `type` explicitly. Unstable API note: - This surface may evolve while the underlying evaluation data model is being redesigned. Parameters ---------- - name : str - Evaluator name within the authenticated project. - - prompt : str - Prompt template used by the evaluator. - - output_definition : EvaluatorOutputDefinition - Structured output schema the evaluator must return. - - Always send `dataType`. - Do not send `version`; it is an internal storage detail and not part of the public request contract. - - model_config : typing.Optional[EvaluatorModelConfig] - Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + request : CreateEvaluatorRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -95,6 +85,7 @@ def create( EvaluatorOutputDefinition_Numeric, EvaluatorOutputFieldDefinition, ) + from langfuse.unstable.evaluators import CreateEvaluatorRequest_LlmAsJudge client = LangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", @@ -105,29 +96,27 @@ def create( base_url="https://yourhost.com/path/to/api", ) client.unstable.evaluators.create( - name="answer-correctness", - prompt="You are grading an answer.\n\nInput:\n{{input}}\n\nOutput:\n{{output}}\n\nReturn a score between 0 and 1.\n", - output_definition=EvaluatorOutputDefinition_Numeric( - data_type=EvaluatorOutputDataType.NUMERIC, - reasoning=EvaluatorOutputFieldDefinition( - description="Explain why the score was assigned.", + request=CreateEvaluatorRequest_LlmAsJudge( + name="answer-correctness", + prompt="You are grading an answer.\n\nInput:\n{{input}}\n\nOutput:\n{{output}}\n\nReturn a score between 0 and 1.\n", + output_definition=EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the score was assigned.", + ), + score=EvaluatorOutputFieldDefinition( + description="Correctness score between 0 and 1.", + ), ), - score=EvaluatorOutputFieldDefinition( - description="Correctness score between 0 and 1.", + model_config=EvaluatorModelConfig( + provider="openai", + model="gpt-4.1-mini", ), ), - model_config=EvaluatorModelConfig( - provider="openai", - model="gpt-4.1-mini", - ), ) """ _response = self._raw_client.create( - name=name, - prompt=prompt, - output_definition=output_definition, - model_config=model_config, - request_options=request_options, + request=request, request_options=request_options ) return _response.data @@ -241,16 +230,15 @@ def with_raw_response(self) -> AsyncRawEvaluatorsClient: async def create( self, *, - name: str, - prompt: str, - output_definition: EvaluatorOutputDefinition, - model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request: CreateEvaluatorRequest, request_options: typing.Optional[RequestOptions] = None, ) -> Evaluator: """ Create an evaluator in the authenticated project. - Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + Use evaluators to define **how** Langfuse should score data. + LLM-as-a-judge evaluators define a prompt, expected structured output, and optional model configuration. + Code evaluators define source code and a runtime language. Naming behavior: - If this is a new evaluator name in your project, Langfuse creates version `1`. @@ -263,30 +251,22 @@ async def create( 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + Code evaluator validation: + - At creation, Langfuse only validates the request shape + - The `sourceCode` itself is not executed here. It is first run (preflight-tested against a sample observation) when you link the evaluator to an evaluation rule, so runtime errors in the code surface at evaluation-rule creation, not at evaluator creation. + Recovery guidance: - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. - - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - `400` with `code=invalid_body` on `outputDefinition`: for `type=llm_as_judge`, send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - If `type` is omitted, Langfuse treats the request as `type=llm_as_judge` for backwards compatibility. New clients should send `type` explicitly. Unstable API note: - This surface may evolve while the underlying evaluation data model is being redesigned. Parameters ---------- - name : str - Evaluator name within the authenticated project. - - prompt : str - Prompt template used by the evaluator. - - output_definition : EvaluatorOutputDefinition - Structured output schema the evaluator must return. - - Always send `dataType`. - Do not send `version`; it is an internal storage detail and not part of the public request contract. - - model_config : typing.Optional[EvaluatorModelConfig] - Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + request : CreateEvaluatorRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -306,6 +286,7 @@ async def create( EvaluatorOutputDefinition_Numeric, EvaluatorOutputFieldDefinition, ) + from langfuse.unstable.evaluators import CreateEvaluatorRequest_LlmAsJudge client = AsyncLangfuseAPI( x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", @@ -319,32 +300,30 @@ async def create( async def main() -> None: await client.unstable.evaluators.create( - name="answer-correctness", - prompt="You are grading an answer.\n\nInput:\n{{input}}\n\nOutput:\n{{output}}\n\nReturn a score between 0 and 1.\n", - output_definition=EvaluatorOutputDefinition_Numeric( - data_type=EvaluatorOutputDataType.NUMERIC, - reasoning=EvaluatorOutputFieldDefinition( - description="Explain why the score was assigned.", + request=CreateEvaluatorRequest_LlmAsJudge( + name="answer-correctness", + prompt="You are grading an answer.\n\nInput:\n{{input}}\n\nOutput:\n{{output}}\n\nReturn a score between 0 and 1.\n", + output_definition=EvaluatorOutputDefinition_Numeric( + data_type=EvaluatorOutputDataType.NUMERIC, + reasoning=EvaluatorOutputFieldDefinition( + description="Explain why the score was assigned.", + ), + score=EvaluatorOutputFieldDefinition( + description="Correctness score between 0 and 1.", + ), ), - score=EvaluatorOutputFieldDefinition( - description="Correctness score between 0 and 1.", + model_config=EvaluatorModelConfig( + provider="openai", + model="gpt-4.1-mini", ), ), - model_config=EvaluatorModelConfig( - provider="openai", - model="gpt-4.1-mini", - ), ) asyncio.run(main()) """ _response = await self._raw_client.create( - name=name, - prompt=prompt, - output_definition=output_definition, - model_config=model_config, - request_options=request_options, + request=request, request_options=request_options ) return _response.data diff --git a/langfuse/api/unstable/evaluators/raw_client.py b/langfuse/api/unstable/evaluators/raw_client.py index f599e3298..30034d033 100644 --- a/langfuse/api/unstable/evaluators/raw_client.py +++ b/langfuse/api/unstable/evaluators/raw_client.py @@ -23,8 +23,6 @@ from ...core.pydantic_utilities import parse_obj_as from ...core.request_options import RequestOptions from ...core.serialization import convert_and_respect_annotation_metadata -from ..commons.types.evaluator_model_config import EvaluatorModelConfig -from ..commons.types.evaluator_output_definition import EvaluatorOutputDefinition from ..errors.errors.access_denied_error import ( AccessDeniedError as unstable_errors_errors_access_denied_error_AccessDeniedError, ) @@ -43,6 +41,7 @@ ) from ..errors.errors.unprocessable_content_error import UnprocessableContentError from ..errors.types.public_api_error import PublicApiError +from .types.create_evaluator_request import CreateEvaluatorRequest from .types.evaluator import Evaluator from .types.evaluators import Evaluators @@ -57,16 +56,15 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): def create( self, *, - name: str, - prompt: str, - output_definition: EvaluatorOutputDefinition, - model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request: CreateEvaluatorRequest, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Evaluator]: """ Create an evaluator in the authenticated project. - Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + Use evaluators to define **how** Langfuse should score data. + LLM-as-a-judge evaluators define a prompt, expected structured output, and optional model configuration. + Code evaluators define source code and a runtime language. Naming behavior: - If this is a new evaluator name in your project, Langfuse creates version `1`. @@ -79,30 +77,22 @@ def create( 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + Code evaluator validation: + - At creation, Langfuse only validates the request shape + - The `sourceCode` itself is not executed here. It is first run (preflight-tested against a sample observation) when you link the evaluator to an evaluation rule, so runtime errors in the code surface at evaluation-rule creation, not at evaluator creation. + Recovery guidance: - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. - - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - `400` with `code=invalid_body` on `outputDefinition`: for `type=llm_as_judge`, send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - If `type` is omitted, Langfuse treats the request as `type=llm_as_judge` for backwards compatibility. New clients should send `type` explicitly. Unstable API note: - This surface may evolve while the underlying evaluation data model is being redesigned. Parameters ---------- - name : str - Evaluator name within the authenticated project. - - prompt : str - Prompt template used by the evaluator. - - output_definition : EvaluatorOutputDefinition - Structured output schema the evaluator must return. - - Always send `dataType`. - Do not send `version`; it is an internal storage detail and not part of the public request contract. - - model_config : typing.Optional[EvaluatorModelConfig] - Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + request : CreateEvaluatorRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -114,20 +104,9 @@ def create( _response = self._client_wrapper.httpx_client.request( "api/public/unstable/evaluators", method="POST", - json={ - "name": name, - "prompt": prompt, - "outputDefinition": convert_and_respect_annotation_metadata( - object_=output_definition, - annotation=EvaluatorOutputDefinition, - direction="write", - ), - "modelConfig": convert_and_respect_annotation_metadata( - object_=model_config, - annotation=typing.Optional[EvaluatorModelConfig], - direction="write", - ), - }, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=CreateEvaluatorRequest, direction="write" + ), request_options=request_options, omit=OMIT, ) @@ -671,16 +650,15 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): async def create( self, *, - name: str, - prompt: str, - output_definition: EvaluatorOutputDefinition, - model_config: typing.Optional[EvaluatorModelConfig] = OMIT, + request: CreateEvaluatorRequest, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Evaluator]: """ Create an evaluator in the authenticated project. - Use evaluators to define **how** Langfuse should score data: the prompt, the expected structured output, and the optional model configuration. + Use evaluators to define **how** Langfuse should score data. + LLM-as-a-judge evaluators define a prompt, expected structured output, and optional model configuration. + Code evaluators define source code and a runtime language. Naming behavior: - If this is a new evaluator name in your project, Langfuse creates version `1`. @@ -693,30 +671,22 @@ async def create( 3. Read the returned `outputDefinition.dataType` so the client knows whether future scores will be numeric, boolean, or categorical. 4. Create one or more evaluation rules that reference the returned evaluator family using `name` and `scope`. + Code evaluator validation: + - At creation, Langfuse only validates the request shape + - The `sourceCode` itself is not executed here. It is first run (preflight-tested against a sample observation) when you link the evaluator to an evaluation rule, so runtime errors in the code surface at evaluation-rule creation, not at evaluator creation. + Recovery guidance: - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run with the resolved model configuration. Add a valid explicit `modelConfig`, or configure the project's default evaluation model, then retry the same request. - `400` with `code=invalid_body`: the request shape is malformed. Use the structured `details.issues` array to fix the specific fields and retry. - - `400` with `code=invalid_body` on `outputDefinition`: send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - `400` with `code=invalid_body` on `outputDefinition`: for `type=llm_as_judge`, send `dataType`, `reasoning.description`, and `score.description`. Do not send `version`; it is not part of the public request shape. + - If `type` is omitted, Langfuse treats the request as `type=llm_as_judge` for backwards compatibility. New clients should send `type` explicitly. Unstable API note: - This surface may evolve while the underlying evaluation data model is being redesigned. Parameters ---------- - name : str - Evaluator name within the authenticated project. - - prompt : str - Prompt template used by the evaluator. - - output_definition : EvaluatorOutputDefinition - Structured output schema the evaluator must return. - - Always send `dataType`. - Do not send `version`; it is an internal storage detail and not part of the public request contract. - - model_config : typing.Optional[EvaluatorModelConfig] - Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + request : CreateEvaluatorRequest request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -728,20 +698,9 @@ async def create( _response = await self._client_wrapper.httpx_client.request( "api/public/unstable/evaluators", method="POST", - json={ - "name": name, - "prompt": prompt, - "outputDefinition": convert_and_respect_annotation_metadata( - object_=output_definition, - annotation=EvaluatorOutputDefinition, - direction="write", - ), - "modelConfig": convert_and_respect_annotation_metadata( - object_=model_config, - annotation=typing.Optional[EvaluatorModelConfig], - direction="write", - ), - }, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=CreateEvaluatorRequest, direction="write" + ), request_options=request_options, omit=OMIT, ) diff --git a/langfuse/api/unstable/evaluators/types/__init__.py b/langfuse/api/unstable/evaluators/types/__init__.py index 6e7a13233..650598592 100644 --- a/langfuse/api/unstable/evaluators/types/__init__.py +++ b/langfuse/api/unstable/evaluators/types/__init__.py @@ -6,13 +6,31 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .create_evaluator_request import CreateEvaluatorRequest - from .evaluator import Evaluator + from .code_evaluator import CodeEvaluator + from .create_code_evaluator_request import CreateCodeEvaluatorRequest + from .create_evaluator_request import ( + CreateEvaluatorRequest, + CreateEvaluatorRequest_Code, + CreateEvaluatorRequest_LlmAsJudge, + ) + from .create_llm_as_judge_evaluator_request import CreateLlmAsJudgeEvaluatorRequest + from .evaluator import Evaluator, Evaluator_Code, Evaluator_LlmAsJudge + from .evaluator_base import EvaluatorBase from .evaluators import Evaluators + from .llm_as_judge_evaluator import LlmAsJudgeEvaluator _dynamic_imports: typing.Dict[str, str] = { + "CodeEvaluator": ".code_evaluator", + "CreateCodeEvaluatorRequest": ".create_code_evaluator_request", "CreateEvaluatorRequest": ".create_evaluator_request", + "CreateEvaluatorRequest_Code": ".create_evaluator_request", + "CreateEvaluatorRequest_LlmAsJudge": ".create_evaluator_request", + "CreateLlmAsJudgeEvaluatorRequest": ".create_llm_as_judge_evaluator_request", "Evaluator": ".evaluator", + "EvaluatorBase": ".evaluator_base", + "Evaluator_Code": ".evaluator", + "Evaluator_LlmAsJudge": ".evaluator", "Evaluators": ".evaluators", + "LlmAsJudgeEvaluator": ".llm_as_judge_evaluator", } @@ -43,4 +61,17 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["CreateEvaluatorRequest", "Evaluator", "Evaluators"] +__all__ = [ + "CodeEvaluator", + "CreateCodeEvaluatorRequest", + "CreateEvaluatorRequest", + "CreateEvaluatorRequest_Code", + "CreateEvaluatorRequest_LlmAsJudge", + "CreateLlmAsJudgeEvaluatorRequest", + "Evaluator", + "EvaluatorBase", + "Evaluator_Code", + "Evaluator_LlmAsJudge", + "Evaluators", + "LlmAsJudgeEvaluator", +] diff --git a/langfuse/api/unstable/evaluators/types/code_evaluator.py b/langfuse/api/unstable/evaluators/types/code_evaluator.py new file mode 100644 index 000000000..f8648603d --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/code_evaluator.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.serialization import FieldMetadata +from ...commons.types.code_evaluator_source_code_language import ( + CodeEvaluatorSourceCodeLanguage, +) +from .evaluator_base import EvaluatorBase + + +class CodeEvaluator(EvaluatorBase): + source_code: typing_extensions.Annotated[str, FieldMetadata(alias="sourceCode")] = ( + pydantic.Field() + ) + """ + Source code executed for each matched observation. + """ + + source_code_language: typing_extensions.Annotated[ + CodeEvaluatorSourceCodeLanguage, FieldMetadata(alias="sourceCodeLanguage") + ] = pydantic.Field() + """ + Runtime language for `sourceCode`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/types/create_code_evaluator_request.py b/langfuse/api/unstable/evaluators/types/create_code_evaluator_request.py new file mode 100644 index 000000000..860c15f9a --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/create_code_evaluator_request.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ...commons.types.code_evaluator_source_code_language import ( + CodeEvaluatorSourceCodeLanguage, +) + + +class CreateCodeEvaluatorRequest(UniversalBaseModel): + name: str = pydantic.Field() + """ + Evaluator name within the authenticated project. + """ + + source_code: typing_extensions.Annotated[str, FieldMetadata(alias="sourceCode")] = ( + pydantic.Field() + ) + """ + Code executed for each matched observation. + """ + + source_code_language: typing_extensions.Annotated[ + CodeEvaluatorSourceCodeLanguage, FieldMetadata(alias="sourceCodeLanguage") + ] = pydantic.Field() + """ + Runtime language for `sourceCode`. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/types/create_evaluator_request.py b/langfuse/api/unstable/evaluators/types/create_evaluator_request.py index 7616d99ee..a866aa4c5 100644 --- a/langfuse/api/unstable/evaluators/types/create_evaluator_request.py +++ b/langfuse/api/unstable/evaluators/types/create_evaluator_request.py @@ -1,50 +1,66 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations + import typing import pydantic import typing_extensions from ....core.pydantic_utilities import UniversalBaseModel from ....core.serialization import FieldMetadata +from ...commons.types.code_evaluator_source_code_language import ( + CodeEvaluatorSourceCodeLanguage, +) from ...commons.types.evaluator_model_config import EvaluatorModelConfig from ...commons.types.evaluator_output_definition import EvaluatorOutputDefinition -class CreateEvaluatorRequest(UniversalBaseModel): +class CreateEvaluatorRequest_LlmAsJudge(UniversalBaseModel): """ Request body for creating an evaluator. If the same `name` already exists in your project, Langfuse creates the next version and returns it. Existing evaluation rules in the same project are then moved to that new latest version automatically. + If `type` is omitted, Langfuse defaults it to `llm_as_judge` for backwards compatibility. """ - name: str = pydantic.Field() - """ - Evaluator name within the authenticated project. - """ - - prompt: str = pydantic.Field() - """ - Prompt template used by the evaluator. - """ - + type: typing.Literal["llm_as_judge"] = "llm_as_judge" + name: str + prompt: str output_definition: typing_extensions.Annotated[ EvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") - ] = pydantic.Field() - """ - Structured output schema the evaluator must return. - - Always send `dataType`. - Do not send `version`; it is an internal storage detail and not part of the public request contract. - """ - + ] model_config_: typing_extensions.Annotated[ typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") - ] = pydantic.Field(default=None) + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) + + +class CreateEvaluatorRequest_Code(UniversalBaseModel): """ - Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + Request body for creating an evaluator. + + If the same `name` already exists in your project, Langfuse creates the next version and returns it. + Existing evaluation rules in the same project are then moved to that new latest version automatically. + If `type` is omitted, Langfuse defaults it to `llm_as_judge` for backwards compatibility. """ + type: typing.Literal["code"] = "code" + name: str + source_code: typing_extensions.Annotated[str, FieldMetadata(alias="sourceCode")] + source_code_language: typing_extensions.Annotated[ + CodeEvaluatorSourceCodeLanguage, FieldMetadata(alias="sourceCodeLanguage") + ] + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) + + +CreateEvaluatorRequest = typing_extensions.Annotated[ + typing.Union[CreateEvaluatorRequest_LlmAsJudge, CreateEvaluatorRequest_Code], + pydantic.Field(discriminator="type"), +] diff --git a/langfuse/api/unstable/evaluators/types/create_llm_as_judge_evaluator_request.py b/langfuse/api/unstable/evaluators/types/create_llm_as_judge_evaluator_request.py new file mode 100644 index 000000000..09e121b1b --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/create_llm_as_judge_evaluator_request.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ...commons.types.evaluator_model_config import EvaluatorModelConfig +from ...commons.types.evaluator_output_definition import EvaluatorOutputDefinition + + +class CreateLlmAsJudgeEvaluatorRequest(UniversalBaseModel): + name: str = pydantic.Field() + """ + Evaluator name within the authenticated project. + """ + + prompt: str = pydantic.Field() + """ + Prompt template used by the evaluator. + """ + + output_definition: typing_extensions.Annotated[ + EvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") + ] = pydantic.Field() + """ + Structured output schema the evaluator must return. + + Always send `dataType`. + Do not send `version`; it is an internal storage detail and not part of the public request contract. + """ + + model_config_: typing_extensions.Annotated[ + typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") + ] = pydantic.Field(default=None) + """ + Optional explicit model configuration. Omit or set to `null` to use the project default evaluation model. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/types/evaluator.py b/langfuse/api/unstable/evaluators/types/evaluator.py index 8023839fc..69295e0fd 100644 --- a/langfuse/api/unstable/evaluators/types/evaluator.py +++ b/langfuse/api/unstable/evaluators/types/evaluator.py @@ -1,5 +1,7 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations + import datetime as dt import typing @@ -7,30 +9,27 @@ import typing_extensions from ....core.pydantic_utilities import UniversalBaseModel from ....core.serialization import FieldMetadata +from ...commons.types.code_evaluator_source_code_language import ( + CodeEvaluatorSourceCodeLanguage, +) from ...commons.types.evaluator_model_config import EvaluatorModelConfig from ...commons.types.evaluator_scope import EvaluatorScope -from ...commons.types.evaluator_type import EvaluatorType from ...commons.types.public_evaluator_output_definition import ( PublicEvaluatorOutputDefinition, ) -class Evaluator(UniversalBaseModel): +class Evaluator_LlmAsJudge(UniversalBaseModel): """ One evaluator that can be used for scoring. - An evaluator describes **how** to score data: - - prompt - - extracted prompt variables - - output schema - - optional explicit model configuration + An evaluator describes **how** to score data. It does not define **which** live objects are evaluated. That is the job of `evaluation-rules`. For agent clients, the most important fields are: - - `variables`: use these exact names when building the evaluation-rule `mapping` array - - `outputDefinition`: tells you the expected score type and the evaluator's response instructions - - `modelConfig`: tells you whether the evaluator uses the project default model (`null`) or an explicit provider/model + - `type`: determines which evaluator fields are present + - `variables`: for LLM evaluators, use these exact names when building the evaluation-rule `mapping` array. LLM evaluators require every variable to be mapped. Code evaluators always expose the fixed runtime payload fields and Langfuse maps them automatically. Versioning behavior: - `GET /evaluators` returns the latest version of each available evaluator. @@ -38,81 +37,78 @@ class Evaluator(UniversalBaseModel): - Evaluation rules always run against the latest version for the selected evaluator name within the same source (`project` or `managed`). """ - id: str = pydantic.Field() - """ - Identifier of this evaluator. - """ + type: typing.Literal["llm_as_judge"] = "llm_as_judge" + prompt: str + output_definition: typing_extensions.Annotated[ + PublicEvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") + ] + model_config_: typing_extensions.Annotated[ + typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") + ] = None + id: str + name: str + version: int + scope: EvaluatorScope + variables: typing.List[str] + evaluation_rule_count: typing_extensions.Annotated[ + int, FieldMetadata(alias="evaluationRuleCount") + ] + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] - name: str = pydantic.Field() - """ - Evaluator name. - """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) - version: int = pydantic.Field() - """ - Version number of this evaluator. - """ - scope: EvaluatorScope = pydantic.Field() - """ - Where this evaluator comes from: your project or Langfuse-managed defaults. +class Evaluator_Code(UniversalBaseModel): """ + One evaluator that can be used for scoring. - type: EvaluatorType = pydantic.Field() - """ - Evaluator engine type. Currently always `llm_as_judge`. - """ + An evaluator describes **how** to score data. - prompt: str = pydantic.Field() - """ - Prompt template used during evaluation. - """ - - variables: typing.List[str] = pydantic.Field() - """ - Variables extracted from the evaluator prompt. - - Every variable in this list must be mapped exactly once when creating an evaluation rule. - """ + It does not define **which** live objects are evaluated. That is the job of `evaluation-rules`. - output_definition: typing_extensions.Annotated[ - PublicEvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") - ] = pydantic.Field() - """ - Structured output schema returned by this evaluator. - - Responses always include `dataType` and omit the internal output-definition `version`. - Use `dataType` to decide how future scores should be interpreted. - """ + For agent clients, the most important fields are: + - `type`: determines which evaluator fields are present + - `variables`: for LLM evaluators, use these exact names when building the evaluation-rule `mapping` array. LLM evaluators require every variable to be mapped. Code evaluators always expose the fixed runtime payload fields and Langfuse maps them automatically. - model_config_: typing_extensions.Annotated[ - typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") - ] = pydantic.Field(default=None) - """ - Explicit model configuration, or `null` when the project default evaluation model is used. + Versioning behavior: + - `GET /evaluators` returns the latest version of each available evaluator. + - `GET /evaluators/{id}` can return an older version. + - Evaluation rules always run against the latest version for the selected evaluator name within the same source (`project` or `managed`). """ + type: typing.Literal["code"] = "code" + source_code: typing_extensions.Annotated[str, FieldMetadata(alias="sourceCode")] + source_code_language: typing_extensions.Annotated[ + CodeEvaluatorSourceCodeLanguage, FieldMetadata(alias="sourceCodeLanguage") + ] + id: str + name: str + version: int + scope: EvaluatorScope + variables: typing.List[str] evaluation_rule_count: typing_extensions.Annotated[ int, FieldMetadata(alias="evaluationRuleCount") - ] = pydantic.Field() - """ - Number of evaluation rules in the project that currently use this evaluator version. - """ - + ] created_at: typing_extensions.Annotated[ dt.datetime, FieldMetadata(alias="createdAt") - ] = pydantic.Field() - """ - Timestamp when this evaluator was created. - """ - + ] updated_at: typing_extensions.Annotated[ dt.datetime, FieldMetadata(alias="updatedAt") - ] = pydantic.Field() - """ - Timestamp when this evaluator was last updated. - """ + ] model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) + + +Evaluator = typing_extensions.Annotated[ + typing.Union[Evaluator_LlmAsJudge, Evaluator_Code], + pydantic.Field(discriminator="type"), +] diff --git a/langfuse/api/unstable/evaluators/types/evaluator_base.py b/langfuse/api/unstable/evaluators/types/evaluator_base.py new file mode 100644 index 000000000..7a8362657 --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/evaluator_base.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ...commons.types.evaluator_scope import EvaluatorScope + + +class EvaluatorBase(UniversalBaseModel): + id: str = pydantic.Field() + """ + Identifier of this evaluator. + """ + + name: str = pydantic.Field() + """ + Evaluator name. + """ + + version: int = pydantic.Field() + """ + Version number of this evaluator. + """ + + scope: EvaluatorScope = pydantic.Field() + """ + Where this evaluator comes from: your project or Langfuse-managed defaults. + """ + + variables: typing.List[str] = pydantic.Field() + """ + Variables that can be mapped when creating an evaluation rule. + + LLM evaluators require every variable to be mapped exactly once. Code evaluators always expose the fixed runtime payload fields and Langfuse maps them automatically. + """ + + evaluation_rule_count: typing_extensions.Annotated[ + int, FieldMetadata(alias="evaluationRuleCount") + ] = pydantic.Field() + """ + Number of evaluation rules in the project that currently use this evaluator version. + """ + + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] = pydantic.Field() + """ + Timestamp when this evaluator was created. + """ + + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] = pydantic.Field() + """ + Timestamp when this evaluator was last updated. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/evaluators/types/llm_as_judge_evaluator.py b/langfuse/api/unstable/evaluators/types/llm_as_judge_evaluator.py new file mode 100644 index 000000000..0cf186f47 --- /dev/null +++ b/langfuse/api/unstable/evaluators/types/llm_as_judge_evaluator.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.serialization import FieldMetadata +from ...commons.types.evaluator_model_config import EvaluatorModelConfig +from ...commons.types.public_evaluator_output_definition import ( + PublicEvaluatorOutputDefinition, +) +from .evaluator_base import EvaluatorBase + + +class LlmAsJudgeEvaluator(EvaluatorBase): + prompt: str = pydantic.Field() + """ + Prompt template used during evaluation. + """ + + output_definition: typing_extensions.Annotated[ + PublicEvaluatorOutputDefinition, FieldMetadata(alias="outputDefinition") + ] = pydantic.Field() + """ + Structured output schema returned by this evaluator. + + Responses always include `dataType` and omit the internal output-definition `version`. + Use `dataType` to decide how future scores should be interpreted. + """ + + model_config_: typing_extensions.Annotated[ + typing.Optional[EvaluatorModelConfig], FieldMetadata(alias="modelConfig") + ] = pydantic.Field(default=None) + """ + Explicit model configuration, or `null` when the project default evaluation model is used. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) From 8b7c000ce3906872eff56f841cda500a2fced90a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 08:07:29 +0000 Subject: [PATCH 293/296] chore(deps): bump astral-sh/setup-uv from 8.1.0 to 8.2.0 in the github-actions group (#1696) chore(deps): bump astral-sh/setup-uv in the github-actions group Bumps the github-actions group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv). Updates `astral-sh/setup-uv` from 8.1.0 to 8.2.0 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/08807647e7069bb48b6ef5acd8ec9567f424441b...fac544c07dec837d0ccb6301d7b5580bf5edae39) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/release.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e45d58f59..942df09b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: version: "0.11.2" python-version: "3.13" @@ -41,7 +41,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: version: "0.11.2" python-version: "3.13" @@ -82,7 +82,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: version: "0.11.2" python-version: ${{ matrix.python-version }} @@ -145,7 +145,7 @@ jobs: with: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: version: "0.11.2" python-version: "3.13" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acc942b8f..96848192f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,7 +97,7 @@ jobs: persist-credentials: false - name: Install uv and set Python version - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: version: "0.11.2" python-version: "3.12" From d18654c125e6c6cc223c12dc1add18e9f156d2db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:22:33 +0200 Subject: [PATCH 294/296] chore(deps): bump github/codeql-action from 4.36.1 to 4.36.2 in the github-actions group (#1702) chore(deps): bump github/codeql-action in the github-actions group Bumps the github-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.36.1 to 4.36.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/87557b9c84dde89fdd9b10e88954ac2f4248e463...8aad20d150bbac5944a9f9d289da16a4b0d87c1e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d9f44e50c..0d0b1938d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,7 +61,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1 + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -89,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1 + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: category: "/language:${{matrix.language}}" From 9cfee9fac2d5d26b93eb520a1f24cb387ce9da28 Mon Sep 17 00:00:00 2001 From: Milana Gurbanova <121519865+milanagm@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:02:03 +0200 Subject: [PATCH 295/296] fix: update env template to use LANGFUSE_BASE_URL (#1701) * add LANGFUSE_BASE_URL to .env.template so E2E tests run out-of-the-box Closes LFE-10249 * removed deprecated LANGFUSE_HOST from env template --- .env.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.template b/.env.template index e68327220..77541addf 100644 --- a/.env.template +++ b/.env.template @@ -1,5 +1,5 @@ # Langfuse -LANGFUSE_HOST=http://localhost:3000 +LANGFUSE_BASE_URL=http://localhost:3000 LANGFUSE_PUBLIC_KEY=pk-lf-1234567890 LANGFUSE_SECRET_KEY=sk-lf-1234567890 From 5c9efae400e6288f840c625d196b235a8fecbf52 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 11 Jun 2026 15:31:03 +0000 Subject: [PATCH 296/296] chore: release v4.8.0b1 --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c66a4aed..ceb7ea368 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langfuse" -version = "4.7.1" +version = "4.8.0b1" description = "A client library for accessing langfuse" readme = "README.md" authors = [{ name = "langfuse", email = "developers@langfuse.com" }] diff --git a/uv.lock b/uv.lock index 2da4263fb..7c321118f 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10, <4.0" [options] -exclude-newer = "2026-05-22T18:06:12.465516488Z" +exclude-newer = "2026-06-04T15:30:59.411452714Z" exclude-newer-span = "P7D" [[package]] @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "langfuse" -version = "4.7.1" +version = "4.8.0b1" source = { editable = "." } dependencies = [ { name = "backoff" },