diff --git a/langfuse/api/commons/types/score_config.py b/langfuse/api/commons/types/score_config.py index 36e45e168..008ac6df0 100644 --- a/langfuse/api/commons/types/score_config.py +++ b/langfuse/api/commons/types/score_config.py @@ -17,7 +17,11 @@ class ScoreConfig(UniversalBaseModel): """ id: str - name: str + name: str = pydantic.Field() + """ + Score config name. Maximum 80 characters. + """ + created_at: typing_extensions.Annotated[ dt.datetime, FieldMetadata(alias="createdAt") ] diff --git a/langfuse/api/llm_connections/client.py b/langfuse/api/llm_connections/client.py index 3d4e60d00..5590147e2 100644 --- a/langfuse/api/llm_connections/client.py +++ b/langfuse/api/llm_connections/client.py @@ -190,6 +190,169 @@ def delete( _response = self._raw_client.delete(id, request_options=request_options) return _response.data + def list_organization( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedLlmConnections: + """ + Get all organization-level LLM connections. Requires an organization-scoped API key. + + 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_organization() + """ + _response = self._raw_client.list_organization( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + def upsert_organization( + 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 organization-level LLM connection. The connection is upserted on provider and inherited by projects without a provider override. Requires an organization-scoped API key. + + 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"}`) - **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. + + 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_organization( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ) + """ + _response = self._raw_client.upsert_organization( + 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 + + def delete_organization( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteLlmConnectionResponse: + """ + Delete an organization-level LLM connection by id. Evaluators in projects without an override for the provider are automatically paused. Requires an organization-scoped API key. + + 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_organization( + id="id", + ) + """ + _response = self._raw_client.delete_organization( + id, request_options=request_options + ) + return _response.data + class AsyncLlmConnectionsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -390,3 +553,190 @@ async def main() -> None: """ _response = await self._raw_client.delete(id, request_options=request_options) return _response.data + + async def list_organization( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PaginatedLlmConnections: + """ + Get all organization-level LLM connections. Requires an organization-scoped API key. + + 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_organization() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_organization( + page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def upsert_organization( + 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 organization-level LLM connection. The connection is upserted on provider and inherited by projects without a provider override. Requires an organization-scoped API key. + + 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"}`) - **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. + + 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_organization( + provider="provider", + adapter=LlmAdapter.ANTHROPIC, + secret_key="secretKey", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.upsert_organization( + 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 + + async def delete_organization( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteLlmConnectionResponse: + """ + Delete an organization-level LLM connection by id. Evaluators in projects without an override for the provider are automatically paused. Requires an organization-scoped API key. + + 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_organization( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete_organization( + 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 9a6388658..128c57a4c 100644 --- a/langfuse/api/llm_connections/raw_client.py +++ b/langfuse/api/llm_connections/raw_client.py @@ -382,12 +382,722 @@ def delete( body=_response_json, ) + def list_organization( + self, + *, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PaginatedLlmConnections]: + """ + Get all organization-level LLM connections. Requires an organization-scoped API key. + + 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/organizations/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_organization( + 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 organization-level LLM connection. The connection is upserted on provider and inherited by projects without a provider override. Requires an organization-scoped API key. + + 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"}`) - **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. + + Returns + ------- + HttpResponse[LlmConnection] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/organizations/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, + ) + + def delete_organization( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteLlmConnectionResponse]: + """ + Delete an organization-level LLM connection by id. Evaluators in projects without an override for the provider are automatically paused. Requires an organization-scoped API key. + + Parameters + ---------- + id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteLlmConnectionResponse] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/organizations/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): + 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, + ) -class AsyncRawLlmConnectionsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + 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. - async def list( + 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"}`) - **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. + + 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, + ) + + 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, + ) + + async def list_organization( self, *, page: typing.Optional[int] = None, @@ -395,7 +1105,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[PaginatedLlmConnections]: """ - Get all LLM connections in a project + Get all organization-level LLM connections. Requires an organization-scoped API key. Parameters ---------- @@ -413,7 +1123,7 @@ async def list( AsyncHttpResponse[PaginatedLlmConnections] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/llm-connections", + "api/public/organizations/llm-connections", method="GET", params={ "page": page, @@ -499,7 +1209,7 @@ async def list( body=_response_json, ) - async def upsert( + async def upsert_organization( self, *, provider: str, @@ -513,7 +1223,7 @@ async def upsert( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[LlmConnection]: """ - Create or update an LLM connection. The connection is upserted on provider. + Create or update an organization-level LLM connection. The connection is upserted on provider and inherited by projects without a provider override. Requires an organization-scoped API key. Parameters ---------- @@ -549,7 +1259,7 @@ async def upsert( AsyncHttpResponse[LlmConnection] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/llm-connections", + "api/public/organizations/llm-connections", method="PUT", json={ "provider": provider, @@ -642,11 +1352,11 @@ async def upsert( body=_response_json, ) - async def delete( + async def delete_organization( 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. + Delete an organization-level LLM connection by id. Evaluators in projects without an override for the provider are automatically paused. Requires an organization-scoped API key. Parameters ---------- @@ -660,7 +1370,7 @@ async def delete( AsyncHttpResponse[DeleteLlmConnectionResponse] """ _response = await self._client_wrapper.httpx_client.request( - f"api/public/llm-connections/{jsonable_encoder(id)}", + f"api/public/organizations/llm-connections/{jsonable_encoder(id)}", method="DELETE", request_options=request_options, ) diff --git a/langfuse/api/projects/client.py b/langfuse/api/projects/client.py index 35d9683b1..80f67105d 100644 --- a/langfuse/api/projects/client.py +++ b/langfuse/api/projects/client.py @@ -184,7 +184,7 @@ def delete( request_options: typing.Optional[RequestOptions] = None, ) -> ProjectDeletionResponse: """ - Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. Deleting the AI Gateway ingestion project turns off the AI Gateway for the organization until another ingestion project is selected. Parameters ---------- @@ -561,7 +561,7 @@ async def delete( request_options: typing.Optional[RequestOptions] = None, ) -> ProjectDeletionResponse: """ - Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. Deleting the AI Gateway ingestion project turns off the AI Gateway for the organization until another ingestion project is selected. Parameters ---------- diff --git a/langfuse/api/projects/raw_client.py b/langfuse/api/projects/raw_client.py index 71457a2ac..c9d1d3d46 100644 --- a/langfuse/api/projects/raw_client.py +++ b/langfuse/api/projects/raw_client.py @@ -375,7 +375,7 @@ def delete( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[ProjectDeletionResponse]: """ - Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. Deleting the AI Gateway ingestion project turns off the AI Gateway for the organization until another ingestion project is selected. Parameters ---------- @@ -1161,7 +1161,7 @@ async def delete( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[ProjectDeletionResponse]: """ - Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. Deleting the AI Gateway ingestion project turns off the AI Gateway for the organization until another ingestion project is selected. Parameters ---------- diff --git a/langfuse/api/score_configs/client.py b/langfuse/api/score_configs/client.py index b900e3f5d..9db9cfe61 100644 --- a/langfuse/api/score_configs/client.py +++ b/langfuse/api/score_configs/client.py @@ -46,7 +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. + Name of the score config. Max 80 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -205,7 +205,7 @@ def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + Name of the score config. Max 80 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 @@ -287,7 +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. + Name of the score config. Max 80 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -470,7 +470,7 @@ async def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + Name of the score config. Max 80 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 11de026c6..1f13905f7 100644 --- a/langfuse/api/score_configs/raw_client.py +++ b/langfuse/api/score_configs/raw_client.py @@ -45,7 +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. + Name of the score config. Max 80 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -401,7 +401,7 @@ def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + Name of the score config. Max 80 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 @@ -540,7 +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. + Name of the score config. Max 80 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. data_type : ScoreConfigDataType @@ -896,7 +896,7 @@ async def update( The status of the score config showing if it is archived or not name : typing.Optional[str] - Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + Name of the score config. Max 80 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 0edb01407..175e86e46 100644 --- a/langfuse/api/score_configs/types/create_score_config_request.py +++ b/langfuse/api/score_configs/types/create_score_config_request.py @@ -13,7 +13,7 @@ class CreateScoreConfigRequest(UniversalBaseModel): name: str = pydantic.Field() """ - Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + Name of the score config. Max 80 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. """ data_type: typing_extensions.Annotated[ 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 28c4248e9..134920fb1 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) """ - Name of the score config. Max 35 characters. Only letters, numbers, underscores, spaces, periods, parentheses, and hyphens are allowed. + Name of the score config. Max 80 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/unstable/__init__.py b/langfuse/api/unstable/__init__.py index 2a249200a..39fafb6f8 100644 --- a/langfuse/api/unstable/__init__.py +++ b/langfuse/api/unstable/__init__.py @@ -20,7 +20,7 @@ TooManyRequestsError, UnauthorizedError, ) - from . import dashboard_widgets, dashboards, errors + from . import dashboard_widgets, dashboards, errors, topics from .dashboard_widgets import ( CreateDashboardWidgetRequest, DashboardWidget, @@ -59,6 +59,19 @@ UpdateDashboardRequest, WidgetPlacement, ) + from .topics import ( + Topic, + TopicFacet, + TopicFacetCounts, + TopicFacetList, + TopicFacetPublishedRun, + TopicFacetRef, + TopicList, + TopicStatusCounts, + TopicWindow, + TopicWithStatus, + TopicsOrderBy, + ) _dynamic_imports: typing.Dict[str, str] = { "AccessDeniedError": ".errors", "BadRequestError": ".errors", @@ -101,6 +114,17 @@ "PublicApiErrorDetails": ".errors", "PublicApiValidationIssue": ".errors", "TooManyRequestsError": ".errors", + "Topic": ".topics", + "TopicFacet": ".topics", + "TopicFacetCounts": ".topics", + "TopicFacetList": ".topics", + "TopicFacetPublishedRun": ".topics", + "TopicFacetRef": ".topics", + "TopicList": ".topics", + "TopicStatusCounts": ".topics", + "TopicWindow": ".topics", + "TopicWithStatus": ".topics", + "TopicsOrderBy": ".topics", "UnauthorizedError": ".errors", "UpdateDashboardPlacementRequest": ".dashboards", "UpdateDashboardRequest": ".dashboards", @@ -109,6 +133,7 @@ "dashboard_widgets": ".dashboard_widgets", "dashboards": ".dashboards", "errors": ".errors", + "topics": ".topics", } @@ -181,6 +206,17 @@ def __dir__(): "PublicApiErrorDetails", "PublicApiValidationIssue", "TooManyRequestsError", + "Topic", + "TopicFacet", + "TopicFacetCounts", + "TopicFacetList", + "TopicFacetPublishedRun", + "TopicFacetRef", + "TopicList", + "TopicStatusCounts", + "TopicWindow", + "TopicWithStatus", + "TopicsOrderBy", "UnauthorizedError", "UpdateDashboardPlacementRequest", "UpdateDashboardRequest", @@ -189,4 +225,5 @@ def __dir__(): "dashboard_widgets", "dashboards", "errors", + "topics", ] diff --git a/langfuse/api/unstable/client.py b/langfuse/api/unstable/client.py index 23050bc80..314109b0f 100644 --- a/langfuse/api/unstable/client.py +++ b/langfuse/api/unstable/client.py @@ -13,6 +13,7 @@ DashboardWidgetsClient, ) from .dashboards.client import AsyncDashboardsClient, DashboardsClient + from .topics.client import AsyncTopicsClient, TopicsClient class UnstableClient: @@ -21,6 +22,7 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper self._dashboard_widgets: typing.Optional[DashboardWidgetsClient] = None self._dashboards: typing.Optional[DashboardsClient] = None + self._topics: typing.Optional[TopicsClient] = None @property def with_raw_response(self) -> RawUnstableClient: @@ -51,6 +53,14 @@ def dashboards(self): self._dashboards = DashboardsClient(client_wrapper=self._client_wrapper) return self._dashboards + @property + def topics(self): + if self._topics is None: + from .topics.client import TopicsClient # noqa: E402 + + self._topics = TopicsClient(client_wrapper=self._client_wrapper) + return self._topics + class AsyncUnstableClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -58,6 +68,7 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper self._dashboard_widgets: typing.Optional[AsyncDashboardWidgetsClient] = None self._dashboards: typing.Optional[AsyncDashboardsClient] = None + self._topics: typing.Optional[AsyncTopicsClient] = None @property def with_raw_response(self) -> AsyncRawUnstableClient: @@ -89,3 +100,11 @@ def dashboards(self): client_wrapper=self._client_wrapper ) return self._dashboards + + @property + def topics(self): + if self._topics is None: + from .topics.client import AsyncTopicsClient # noqa: E402 + + self._topics = AsyncTopicsClient(client_wrapper=self._client_wrapper) + return self._topics diff --git a/langfuse/api/unstable/topics/__init__.py b/langfuse/api/unstable/topics/__init__.py new file mode 100644 index 000000000..baa5ee5e9 --- /dev/null +++ b/langfuse/api/unstable/topics/__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 ( + Topic, + TopicFacet, + TopicFacetCounts, + TopicFacetList, + TopicFacetPublishedRun, + TopicFacetRef, + TopicList, + TopicStatusCounts, + TopicWindow, + TopicWithStatus, + TopicsOrderBy, + ) +_dynamic_imports: typing.Dict[str, str] = { + "Topic": ".types", + "TopicFacet": ".types", + "TopicFacetCounts": ".types", + "TopicFacetList": ".types", + "TopicFacetPublishedRun": ".types", + "TopicFacetRef": ".types", + "TopicList": ".types", + "TopicStatusCounts": ".types", + "TopicWindow": ".types", + "TopicWithStatus": ".types", + "TopicsOrderBy": ".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__ = [ + "Topic", + "TopicFacet", + "TopicFacetCounts", + "TopicFacetList", + "TopicFacetPublishedRun", + "TopicFacetRef", + "TopicList", + "TopicStatusCounts", + "TopicWindow", + "TopicWithStatus", + "TopicsOrderBy", +] diff --git a/langfuse/api/unstable/topics/client.py b/langfuse/api/unstable/topics/client.py new file mode 100644 index 000000000..9ca28537a --- /dev/null +++ b/langfuse/api/unstable/topics/client.py @@ -0,0 +1,543 @@ +# 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 AsyncRawTopicsClient, RawTopicsClient +from .types.topic_facet import TopicFacet +from .types.topic_facet_list import TopicFacetList +from .types.topic_list import TopicList +from .types.topic_with_status import TopicWithStatus +from .types.topics_order_by import TopicsOrderBy + + +class TopicsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTopicsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTopicsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTopicsClient + """ + return self._raw_client + + def list_facets( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicFacetList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics facets. Only active facets (attached to the + project's Topics rule, or all facets if the project has no rule) are + returned unless `filter` contains an `active` condition. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions), `description` (string), `isBuiltIn` + (boolean), `active` (boolean). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicFacetList + + 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.topics.list_facets() + """ + _response = self._raw_client.list_facets( + filter=filter, page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get_facet( + self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TopicFacet: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics facet by id, including inactive facets. + + Parameters + ---------- + facet_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicFacet + + 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.topics.get_facet( + facet_id="facetId", + ) + """ + _response = self._raw_client.get_facet( + facet_id, request_options=request_options + ) + return _response.data + + def list( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicsOrderBy] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the topics of each active facet's current published map, with + the number of traces currently assigned to each topic in the window. + Retired topics are not listed. `facets` holds per-facet trace counts + by topic status. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 93 days. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `facetId` + (stringOptions), `facetName` (string, stringOptions), `id` + (stringOptions), `name` (string, stringOptions). Facet conditions also + restrict `facets`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"facetName","operator":"any of","value":["Issues"]}]`. + + order_by : typing.Optional[TopicsOrderBy] + Defaults to `traceCount.desc`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicList + + 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.topics.list() + """ + _response = self._raw_client.list( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + order_by=order_by, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + def get( + self, + topic_id: str, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicWithStatus: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one topic by its stable id. Topics on the current map are always + found. A retired topic is found while traces in the window are still + assigned to it, with `inCurrentMap: false`. + + Parameters + ---------- + topic_id : str + + from_timestamp : typing.Optional[dt.datetime] + Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window used for `traceCount`. Defaults to now. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicWithStatus + + 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.topics.get( + topic_id="topicId", + ) + """ + _response = self._raw_client.get( + topic_id, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + request_options=request_options, + ) + return _response.data + + +class AsyncTopicsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTopicsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTopicsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTopicsClient + """ + return self._raw_client + + async def list_facets( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicFacetList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics facets. Only active facets (attached to the + project's Topics rule, or all facets if the project has no rule) are + returned unless `filter` contains an `active` condition. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions), `description` (string), `isBuiltIn` + (boolean), `active` (boolean). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicFacetList + + 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.topics.list_facets() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_facets( + filter=filter, page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get_facet( + self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TopicFacet: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics facet by id, including inactive facets. + + Parameters + ---------- + facet_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicFacet + + 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.topics.get_facet( + facet_id="facetId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_facet( + facet_id, request_options=request_options + ) + return _response.data + + async def list( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicsOrderBy] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the topics of each active facet's current published map, with + the number of traces currently assigned to each topic in the window. + Retired topics are not listed. `facets` holds per-facet trace counts + by topic status. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 93 days. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `facetId` + (stringOptions), `facetName` (string, stringOptions), `id` + (stringOptions), `name` (string, stringOptions). Facet conditions also + restrict `facets`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"facetName","operator":"any of","value":["Issues"]}]`. + + order_by : typing.Optional[TopicsOrderBy] + Defaults to `traceCount.desc`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicList + + 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.topics.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + order_by=order_by, + page=page, + limit=limit, + request_options=request_options, + ) + return _response.data + + async def get( + self, + topic_id: str, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicWithStatus: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one topic by its stable id. Topics on the current map are always + found. A retired topic is found while traces in the window are still + assigned to it, with `inCurrentMap: false`. + + Parameters + ---------- + topic_id : str + + from_timestamp : typing.Optional[dt.datetime] + Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window used for `traceCount`. Defaults to now. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicWithStatus + + 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.topics.get( + topic_id="topicId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + topic_id, + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/unstable/topics/raw_client.py b/langfuse/api/unstable/topics/raw_client.py new file mode 100644 index 000000000..2a28528a4 --- /dev/null +++ b/langfuse/api/unstable/topics/raw_client.py @@ -0,0 +1,1675 @@ +# 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 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.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 ..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.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.types.public_api_error import PublicApiError +from .types.topic_facet import TopicFacet +from .types.topic_facet_list import TopicFacetList +from .types.topic_list import TopicList +from .types.topic_with_status import TopicWithStatus +from .types.topics_order_by import TopicsOrderBy + + +class RawTopicsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list_facets( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicFacetList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics facets. Only active facets (attached to the + project's Topics rule, or all facets if the project has no rule) are + returned unless `filter` contains an `active` condition. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions), `description` (string), `isBuiltIn` + (boolean), `active` (boolean). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicFacetList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-facets", + method="GET", + params={ + "filter": filter, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicFacetList, + parse_obj_as( + type_=TopicFacetList, # 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 get_facet( + self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TopicFacet]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics facet by id, including inactive facets. + + Parameters + ---------- + facet_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicFacet] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/topic-facets/{jsonable_encoder(facet_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicFacet, + parse_obj_as( + type_=TopicFacet, # 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 list( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicsOrderBy] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the topics of each active facet's current published map, with + the number of traces currently assigned to each topic in the window. + Retired topics are not listed. `facets` holds per-facet trace counts + by topic status. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 93 days. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `facetId` + (stringOptions), `facetName` (string, stringOptions), `id` + (stringOptions), `name` (string, stringOptions). Facet conditions also + restrict `facets`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"facetName","operator":"any of","value":["Issues"]}]`. + + order_by : typing.Optional[TopicsOrderBy] + Defaults to `traceCount.desc`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topics", + method="GET", + params={ + "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, + "filter": filter, + "orderBy": order_by, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicList, + parse_obj_as( + type_=TopicList, # 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 get( + self, + topic_id: str, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicWithStatus]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one topic by its stable id. Topics on the current map are always + found. A retired topic is found while traces in the window are still + assigned to it, with `inCurrentMap: false`. + + Parameters + ---------- + topic_id : str + + from_timestamp : typing.Optional[dt.datetime] + Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window used for `traceCount`. Defaults to now. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicWithStatus] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/topics/{jsonable_encoder(topic_id)}", + method="GET", + params={ + "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( + TopicWithStatus, + parse_obj_as( + type_=TopicWithStatus, # 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 AsyncRawTopicsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list_facets( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TopicFacetList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics facets. Only active facets (attached to the + project's Topics rule, or all facets if the project has no rule) are + returned unless `filter` contains an `active` condition. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions), `description` (string), `isBuiltIn` + (boolean), `active` (boolean). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicFacetList] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-facets", + method="GET", + params={ + "filter": filter, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicFacetList, + parse_obj_as( + type_=TopicFacetList, # 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 get_facet( + self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TopicFacet]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics facet by id, including inactive facets. + + Parameters + ---------- + facet_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicFacet] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/topic-facets/{jsonable_encoder(facet_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicFacet, + parse_obj_as( + type_=TopicFacet, # 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 list( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicsOrderBy] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TopicList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the topics of each active facet's current published map, with + the number of traces currently assigned to each topic in the window. + Retired topics are not listed. `facets` holds per-facet trace counts + by topic status. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 93 days. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `facetId` + (stringOptions), `facetName` (string, stringOptions), `id` + (stringOptions), `name` (string, stringOptions). Facet conditions also + restrict `facets`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"facetName","operator":"any of","value":["Issues"]}]`. + + order_by : typing.Optional[TopicsOrderBy] + Defaults to `traceCount.desc`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicList] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/topics", + method="GET", + params={ + "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, + "filter": filter, + "orderBy": order_by, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicList, + parse_obj_as( + type_=TopicList, # 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 get( + self, + topic_id: str, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TopicWithStatus]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one topic by its stable id. Topics on the current map are always + found. A retired topic is found while traces in the window are still + assigned to it, with `inCurrentMap: false`. + + Parameters + ---------- + topic_id : str + + from_timestamp : typing.Optional[dt.datetime] + Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window used for `traceCount`. Defaults to now. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicWithStatus] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/topics/{jsonable_encoder(topic_id)}", + method="GET", + params={ + "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( + TopicWithStatus, + parse_obj_as( + type_=TopicWithStatus, # 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/topics/types/__init__.py b/langfuse/api/unstable/topics/types/__init__.py new file mode 100644 index 000000000..4e6e56333 --- /dev/null +++ b/langfuse/api/unstable/topics/types/__init__.py @@ -0,0 +1,74 @@ +# 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 .topic import Topic + from .topic_facet import TopicFacet + from .topic_facet_counts import TopicFacetCounts + from .topic_facet_list import TopicFacetList + from .topic_facet_published_run import TopicFacetPublishedRun + from .topic_facet_ref import TopicFacetRef + from .topic_list import TopicList + from .topic_status_counts import TopicStatusCounts + from .topic_window import TopicWindow + from .topic_with_status import TopicWithStatus + from .topics_order_by import TopicsOrderBy +_dynamic_imports: typing.Dict[str, str] = { + "Topic": ".topic", + "TopicFacet": ".topic_facet", + "TopicFacetCounts": ".topic_facet_counts", + "TopicFacetList": ".topic_facet_list", + "TopicFacetPublishedRun": ".topic_facet_published_run", + "TopicFacetRef": ".topic_facet_ref", + "TopicList": ".topic_list", + "TopicStatusCounts": ".topic_status_counts", + "TopicWindow": ".topic_window", + "TopicWithStatus": ".topic_with_status", + "TopicsOrderBy": ".topics_order_by", +} + + +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__ = [ + "Topic", + "TopicFacet", + "TopicFacetCounts", + "TopicFacetList", + "TopicFacetPublishedRun", + "TopicFacetRef", + "TopicList", + "TopicStatusCounts", + "TopicWindow", + "TopicWithStatus", + "TopicsOrderBy", +] diff --git a/langfuse/api/unstable/topics/types/topic.py b/langfuse/api/unstable/topics/types/topic.py new file mode 100644 index 000000000..e32938810 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic.py @@ -0,0 +1,44 @@ +# 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 .topic_facet_ref import TopicFacetRef + + +class Topic(UniversalBaseModel): + id: str = pydantic.Field() + """ + Stable topic id, kept across map updates. + """ + + version_id: typing_extensions.Annotated[str, FieldMetadata(alias="versionId")] = ( + pydantic.Field() + ) + """ + Id of this topic version. + """ + + name: str + description: str + facet: TopicFacetRef + run_id: typing_extensions.Annotated[str, FieldMetadata(alias="runId")] = ( + pydantic.Field() + ) + """ + The run that produced this topic version. + """ + + trace_count: typing_extensions.Annotated[int, FieldMetadata(alias="traceCount")] = ( + pydantic.Field() + ) + """ + Traces in the window whose current result for the facet is this topic. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_facet.py b/langfuse/api/unstable/topics/types/topic_facet.py new file mode 100644 index 000000000..2f3673abf --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_facet.py @@ -0,0 +1,41 @@ +# 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 .topic_facet_published_run import TopicFacetPublishedRun + + +class TopicFacet(UniversalBaseModel): + id: str + name: str + description: str + is_built_in: typing_extensions.Annotated[bool, FieldMetadata(alias="isBuiltIn")] + active: bool = pydantic.Field() + """ + Whether the facet is attached to the project's Topics rule. True for all facets if the project has no rule. + """ + + version: int = pydantic.Field() + """ + Latest facet version. + """ + + prompt: str = pydantic.Field() + """ + Prompt of the latest facet version. + """ + + published_run: typing_extensions.Annotated[ + typing.Optional[TopicFacetPublishedRun], FieldMetadata(alias="publishedRun") + ] = pydantic.Field(default=None) + """ + The run that produced the facet's current map. Null before the first map. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_facet_counts.py b/langfuse/api/unstable/topics/types/topic_facet_counts.py new file mode 100644 index 000000000..0bc83e2cf --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_facet_counts.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 +from .topic_facet_ref import TopicFacetRef +from .topic_status_counts import TopicStatusCounts + + +class TopicFacetCounts(UniversalBaseModel): + facet: TopicFacetRef + published_run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="publishedRunId") + ] = None + counts: TopicStatusCounts + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_facet_list.py b/langfuse/api/unstable/topics/types/topic_facet_list.py new file mode 100644 index 000000000..7fed41d86 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_facet_list.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 .topic_facet import TopicFacet + + +class TopicFacetList(UniversalBaseModel): + data: typing.List[TopicFacet] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_facet_published_run.py b/langfuse/api/unstable/topics/types/topic_facet_published_run.py new file mode 100644 index 000000000..956a385ef --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_facet_published_run.py @@ -0,0 +1,21 @@ +# 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 TopicFacetPublishedRun(UniversalBaseModel): + id: str + completed_at: typing_extensions.Annotated[ + typing.Optional[dt.datetime], FieldMetadata(alias="completedAt") + ] = None + topic_count: typing_extensions.Annotated[int, FieldMetadata(alias="topicCount")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_facet_ref.py b/langfuse/api/unstable/topics/types/topic_facet_ref.py new file mode 100644 index 000000000..5371a35fb --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_facet_ref.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 TopicFacetRef(UniversalBaseModel): + id: str + name: str + version: int + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_list.py b/langfuse/api/unstable/topics/types/topic_list.py new file mode 100644 index 000000000..405c2ed3c --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_list.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 ....utils.pagination.types.meta_response import MetaResponse +from .topic import Topic +from .topic_facet_counts import TopicFacetCounts +from .topic_window import TopicWindow + + +class TopicList(UniversalBaseModel): + window: TopicWindow + data: typing.List[Topic] + facets: typing.List[TopicFacetCounts] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_status_counts.py b/langfuse/api/unstable/topics/types/topic_status_counts.py new file mode 100644 index 000000000..df9fc220c --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_status_counts.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 TopicStatusCounts(UniversalBaseModel): + """ + Traces in the window by their current result for one facet. + """ + + assigned: int + outlier: int + awaiting_map: typing_extensions.Annotated[int, FieldMetadata(alias="awaitingMap")] + not_applicable: typing_extensions.Annotated[ + int, FieldMetadata(alias="notApplicable") + ] + insufficient_input: typing_extensions.Annotated[ + int, FieldMetadata(alias="insufficientInput") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_window.py b/langfuse/api/unstable/topics/types/topic_window.py new file mode 100644 index 000000000..956b74df5 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_window.py @@ -0,0 +1,18 @@ +# 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 TopicWindow(UniversalBaseModel): + from_: typing_extensions.Annotated[dt.datetime, FieldMetadata(alias="from")] + to: dt.datetime + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_with_status.py b/langfuse/api/unstable/topics/types/topic_with_status.py new file mode 100644 index 000000000..ca2749b5a --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_with_status.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.serialization import FieldMetadata +from .topic import Topic +from .topic_window import TopicWindow + + +class TopicWithStatus(Topic): + in_current_map: typing_extensions.Annotated[ + bool, FieldMetadata(alias="inCurrentMap") + ] = pydantic.Field() + """ + False for retired topics that still have assigned traces in the window. + """ + + window: TopicWindow + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topics_order_by.py b/langfuse/api/unstable/topics/types/topics_order_by.py new file mode 100644 index 000000000..7ecb2db35 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topics_order_by.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class TopicsOrderBy(enum.StrEnum): + TRACE_COUNT_DESC = "traceCount.desc" + TRACE_COUNT_ASC = "traceCount.asc" + NAME_ASC = "name.asc" + NAME_DESC = "name.desc" + + def visit( + self, + trace_count_desc: typing.Callable[[], T_Result], + trace_count_asc: typing.Callable[[], T_Result], + name_asc: typing.Callable[[], T_Result], + name_desc: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicsOrderBy.TRACE_COUNT_DESC: + return trace_count_desc() + if self is TopicsOrderBy.TRACE_COUNT_ASC: + return trace_count_asc() + if self is TopicsOrderBy.NAME_ASC: + return name_asc() + if self is TopicsOrderBy.NAME_DESC: + return name_desc()