From 9cd0262e3ffebce5870d52130946f1d25ebbc462 Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Wed, 4 Nov 2026 12:38:44 +0000 Subject: [PATCH] feat(api): update API spec from langfuse/langfuse 2cb8041 --- langfuse/api/observations/client.py | 14 +- langfuse/api/observations/raw_client.py | 14 +- .../observations/types/observations_v2meta.py | 5 + .../types/observations_v2response.py | 4 +- langfuse/api/unstable/__init__.py | 33 + langfuse/api/unstable/topics/__init__.py | 33 + langfuse/api/unstable/topics/client.py | 659 +++++- langfuse/api/unstable/topics/raw_client.py | 1901 ++++++++++++++++- .../api/unstable/topics/types/__init__.py | 33 + langfuse/api/unstable/topics/types/topic.py | 7 + .../unstable/topics/types/topic_assignment.py | 50 + .../topics/types/topic_assignment_list.py | 19 + .../topics/types/topic_assignment_status.py | 22 + .../topics/types/topic_assignment_topic.py | 22 + .../api/unstable/topics/types/topic_facet.py | 9 +- .../topics/types/topic_facet_counts.py | 3 + .../topics/types/topic_result_list_meta.py | 22 + .../topics/types/topic_results_order_by.py | 22 + .../api/unstable/topics/types/topic_rule.py | 41 + .../unstable/topics/types/topic_rule_list.py | 17 + .../unstable/topics/types/topic_summary.py | 40 + .../topics/types/topic_summary_list.py | 19 + .../topics/types/topic_summary_state.py | 26 + 23 files changed, 2920 insertions(+), 95 deletions(-) create mode 100644 langfuse/api/unstable/topics/types/topic_assignment.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment_list.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment_status.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment_topic.py create mode 100644 langfuse/api/unstable/topics/types/topic_result_list_meta.py create mode 100644 langfuse/api/unstable/topics/types/topic_results_order_by.py create mode 100644 langfuse/api/unstable/topics/types/topic_rule.py create mode 100644 langfuse/api/unstable/topics/types/topic_rule_list.py create mode 100644 langfuse/api/unstable/topics/types/topic_summary.py create mode 100644 langfuse/api/unstable/topics/types/topic_summary_list.py create mode 100644 langfuse/api/unstable/topics/types/topic_summary_state.py diff --git a/langfuse/api/observations/client.py b/langfuse/api/observations/client.py index 5e717adb6..69323ba72 100644 --- a/langfuse/api/observations/client.py +++ b/langfuse/api/observations/client.py @@ -56,7 +56,10 @@ def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -92,7 +95,7 @@ def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. @@ -342,7 +345,10 @@ async def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -378,7 +384,7 @@ async def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py index 84dba4891..d715fa0b6 100644 --- a/langfuse/api/observations/raw_client.py +++ b/langfuse/api/observations/raw_client.py @@ -54,7 +54,10 @@ def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -90,7 +93,7 @@ def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. @@ -399,7 +402,10 @@ async def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -435,7 +441,7 @@ async def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. diff --git a/langfuse/api/observations/types/observations_v2meta.py b/langfuse/api/observations/types/observations_v2meta.py index 46d9f42aa..2b34f8664 100644 --- a/langfuse/api/observations/types/observations_v2meta.py +++ b/langfuse/api/observations/types/observations_v2meta.py @@ -11,6 +11,11 @@ class ObservationsV2Meta(CursorMetaResponse): Metadata for cursor-based pagination. """ + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Present when an empty page has a cursor; continue requesting pages until the cursor is absent. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) diff --git a/langfuse/api/observations/types/observations_v2response.py b/langfuse/api/observations/types/observations_v2response.py index 0ee1fb8bd..d702b59f7 100644 --- a/langfuse/api/observations/types/observations_v2response.py +++ b/langfuse/api/observations/types/observations_v2response.py @@ -13,7 +13,9 @@ class ObservationsV2Response(UniversalBaseModel): Response containing observations with field-group-based filtering and cursor-based pagination. The `data` array contains observation objects with only the requested field groups included. - Use the `cursor` in `meta` to retrieve the next page of results. + Use the `cursor` in `meta` to retrieve the next page of results. The array may contain fewer + than the requested `limit` items, or be empty, while a cursor is present. Continue until the + cursor is absent. """ data: typing.List[ObservationV2] = pydantic.Field() diff --git a/langfuse/api/unstable/__init__.py b/langfuse/api/unstable/__init__.py index 39fafb6f8..4cc87ca60 100644 --- a/langfuse/api/unstable/__init__.py +++ b/langfuse/api/unstable/__init__.py @@ -61,13 +61,24 @@ ) from .topics import ( Topic, + TopicAssignment, + TopicAssignmentList, + TopicAssignmentStatus, + TopicAssignmentTopic, TopicFacet, TopicFacetCounts, TopicFacetList, TopicFacetPublishedRun, TopicFacetRef, TopicList, + TopicResultListMeta, + TopicResultsOrderBy, + TopicRule, + TopicRuleList, TopicStatusCounts, + TopicSummary, + TopicSummaryList, + TopicSummaryState, TopicWindow, TopicWithStatus, TopicsOrderBy, @@ -115,13 +126,24 @@ "PublicApiValidationIssue": ".errors", "TooManyRequestsError": ".errors", "Topic": ".topics", + "TopicAssignment": ".topics", + "TopicAssignmentList": ".topics", + "TopicAssignmentStatus": ".topics", + "TopicAssignmentTopic": ".topics", "TopicFacet": ".topics", "TopicFacetCounts": ".topics", "TopicFacetList": ".topics", "TopicFacetPublishedRun": ".topics", "TopicFacetRef": ".topics", "TopicList": ".topics", + "TopicResultListMeta": ".topics", + "TopicResultsOrderBy": ".topics", + "TopicRule": ".topics", + "TopicRuleList": ".topics", "TopicStatusCounts": ".topics", + "TopicSummary": ".topics", + "TopicSummaryList": ".topics", + "TopicSummaryState": ".topics", "TopicWindow": ".topics", "TopicWithStatus": ".topics", "TopicsOrderBy": ".topics", @@ -207,13 +229,24 @@ def __dir__(): "PublicApiValidationIssue", "TooManyRequestsError", "Topic", + "TopicAssignment", + "TopicAssignmentList", + "TopicAssignmentStatus", + "TopicAssignmentTopic", "TopicFacet", "TopicFacetCounts", "TopicFacetList", "TopicFacetPublishedRun", "TopicFacetRef", "TopicList", + "TopicResultListMeta", + "TopicResultsOrderBy", + "TopicRule", + "TopicRuleList", "TopicStatusCounts", + "TopicSummary", + "TopicSummaryList", + "TopicSummaryState", "TopicWindow", "TopicWithStatus", "TopicsOrderBy", diff --git a/langfuse/api/unstable/topics/__init__.py b/langfuse/api/unstable/topics/__init__.py index baa5ee5e9..178162448 100644 --- a/langfuse/api/unstable/topics/__init__.py +++ b/langfuse/api/unstable/topics/__init__.py @@ -8,26 +8,48 @@ if typing.TYPE_CHECKING: from .types import ( Topic, + TopicAssignment, + TopicAssignmentList, + TopicAssignmentStatus, + TopicAssignmentTopic, TopicFacet, TopicFacetCounts, TopicFacetList, TopicFacetPublishedRun, TopicFacetRef, TopicList, + TopicResultListMeta, + TopicResultsOrderBy, + TopicRule, + TopicRuleList, TopicStatusCounts, + TopicSummary, + TopicSummaryList, + TopicSummaryState, TopicWindow, TopicWithStatus, TopicsOrderBy, ) _dynamic_imports: typing.Dict[str, str] = { "Topic": ".types", + "TopicAssignment": ".types", + "TopicAssignmentList": ".types", + "TopicAssignmentStatus": ".types", + "TopicAssignmentTopic": ".types", "TopicFacet": ".types", "TopicFacetCounts": ".types", "TopicFacetList": ".types", "TopicFacetPublishedRun": ".types", "TopicFacetRef": ".types", "TopicList": ".types", + "TopicResultListMeta": ".types", + "TopicResultsOrderBy": ".types", + "TopicRule": ".types", + "TopicRuleList": ".types", "TopicStatusCounts": ".types", + "TopicSummary": ".types", + "TopicSummaryList": ".types", + "TopicSummaryState": ".types", "TopicWindow": ".types", "TopicWithStatus": ".types", "TopicsOrderBy": ".types", @@ -63,13 +85,24 @@ def __dir__(): __all__ = [ "Topic", + "TopicAssignment", + "TopicAssignmentList", + "TopicAssignmentStatus", + "TopicAssignmentTopic", "TopicFacet", "TopicFacetCounts", "TopicFacetList", "TopicFacetPublishedRun", "TopicFacetRef", "TopicList", + "TopicResultListMeta", + "TopicResultsOrderBy", + "TopicRule", + "TopicRuleList", "TopicStatusCounts", + "TopicSummary", + "TopicSummaryList", + "TopicSummaryState", "TopicWindow", "TopicWithStatus", "TopicsOrderBy", diff --git a/langfuse/api/unstable/topics/client.py b/langfuse/api/unstable/topics/client.py index 9ca28537a..8d719826e 100644 --- a/langfuse/api/unstable/topics/client.py +++ b/langfuse/api/unstable/topics/client.py @@ -6,9 +6,14 @@ from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ...core.request_options import RequestOptions from .raw_client import AsyncRawTopicsClient, RawTopicsClient +from .types.topic_assignment_list import TopicAssignmentList from .types.topic_facet import TopicFacet from .types.topic_facet_list import TopicFacetList from .types.topic_list import TopicList +from .types.topic_results_order_by import TopicResultsOrderBy +from .types.topic_rule import TopicRule +from .types.topic_rule_list import TopicRuleList +from .types.topic_summary_list import TopicSummaryList from .types.topic_with_status import TopicWithStatus from .types.topics_order_by import TopicsOrderBy @@ -39,9 +44,9 @@ def list_facets( """ 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. + List the project's Topics facets. Only active facets (run by a 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), @@ -139,7 +144,7 @@ def list( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with + List the topics of each active facet's live 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. @@ -149,9 +154,9 @@ def list( `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`. + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. Parameters ---------- @@ -261,6 +266,302 @@ def get( ) return _response.data + def list_rules( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicRuleList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. + + 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 + ------- + TopicRuleList + + 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_rules() + """ + _response = self._raw_client.list_rules( + filter=filter, page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TopicRule: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics rule by id. + + Parameters + ---------- + rule_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicRule + + 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_rule( + rule_id="ruleId", + ) + """ + _response = self._raw_client.get_rule(rule_id, request_options=request_options) + return _response.data + + def list_summaries( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicSummaryList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. + + 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":"traceId","operator":"any of","value":[""]}]`. + + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicSummaryList + + 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_summaries() + """ + _response = self._raw_client.list_summaries( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + truncate_summaries=truncate_summaries, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data + + def list_assignments( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicAssignmentList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. + + 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":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicAssignmentList + + 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_assignments() + """ + _response = self._raw_client.list_assignments( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data + class AsyncTopicsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -288,9 +589,9 @@ async def list_facets( """ 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. + List the project's Topics facets. Only active facets (run by a 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), @@ -404,7 +705,7 @@ async def list( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with + List the topics of each active facet's live 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. @@ -414,9 +715,9 @@ async def list( `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`. + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. Parameters ---------- @@ -541,3 +842,333 @@ async def main() -> None: request_options=request_options, ) return _response.data + + async def list_rules( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicRuleList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. + + 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 + ------- + TopicRuleList + + 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_rules() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_rules( + filter=filter, page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TopicRule: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics rule by id. + + Parameters + ---------- + rule_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicRule + + 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_rule( + rule_id="ruleId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_rule( + rule_id, request_options=request_options + ) + return _response.data + + async def list_summaries( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicSummaryList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. + + 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":"traceId","operator":"any of","value":[""]}]`. + + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicSummaryList + + 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_summaries() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_summaries( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + truncate_summaries=truncate_summaries, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data + + async def list_assignments( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicAssignmentList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. + + 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":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicAssignmentList + + 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_assignments() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_assignments( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + order_by=order_by, + limit=limit, + cursor=cursor, + 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 index 2a28528a4..621bc2e58 100644 --- a/langfuse/api/unstable/topics/raw_client.py +++ b/langfuse/api/unstable/topics/raw_client.py @@ -40,9 +40,14 @@ UnauthorizedError as unstable_errors_errors_unauthorized_error_UnauthorizedError, ) from ..errors.types.public_api_error import PublicApiError +from .types.topic_assignment_list import TopicAssignmentList from .types.topic_facet import TopicFacet from .types.topic_facet_list import TopicFacetList from .types.topic_list import TopicList +from .types.topic_results_order_by import TopicResultsOrderBy +from .types.topic_rule import TopicRule +from .types.topic_rule_list import TopicRuleList +from .types.topic_summary_list import TopicSummaryList from .types.topic_with_status import TopicWithStatus from .types.topics_order_by import TopicsOrderBy @@ -62,9 +67,9 @@ def list_facets( """ 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. + List the project's Topics facets. Only active facets (run by a 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), @@ -447,7 +452,7 @@ def list( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with + List the topics of each active facet's live 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. @@ -457,9 +462,9 @@ def list( `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`. + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. Parameters ---------- @@ -860,35 +865,1720 @@ def get( body=_response_json, ) + def list_rules( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicRuleList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. + + 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[TopicRuleList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-rules", + method="GET", + params={ + "filter": filter, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicRuleList, + parse_obj_as( + type_=TopicRuleList, # 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_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TopicRule]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics rule by id. + + Parameters + ---------- + rule_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicRule] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/topic-rules/{jsonable_encoder(rule_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicRule, + parse_obj_as( + type_=TopicRule, # 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_summaries( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicSummaryList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. + + 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":"traceId","operator":"any of","value":[""]}]`. + + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicSummaryList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-summaries", + 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, + "truncateSummaries": truncate_summaries, + "orderBy": order_by, + "limit": limit, + "cursor": cursor, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicSummaryList, + parse_obj_as( + type_=TopicSummaryList, # 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_assignments( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicAssignmentList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. + + 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":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicAssignmentList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-assignments", + 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, + "limit": limit, + "cursor": cursor, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicAssignmentList, + parse_obj_as( + type_=TopicAssignmentList, # 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 (run by a 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 live 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), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule 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 -class AsyncRawTopicsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + from_timestamp : typing.Optional[dt.datetime] + Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. - async def list_facets( + 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, + ) + + async def list_rules( self, *, filter: typing.Optional[str] = None, page: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TopicFacetList]: + ) -> AsyncHttpResponse[TopicRuleList]: """ 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. + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. `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). + `name` (string, stringOptions). Parameters ---------- filter : typing.Optional[str] - JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. page : typing.Optional[int] 1-based page number. Defaults to `1`. @@ -901,10 +2591,10 @@ async def list_facets( Returns ------- - AsyncHttpResponse[TopicFacetList] + AsyncHttpResponse[TopicRuleList] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/unstable/topic-facets", + "api/public/unstable/topic-rules", method="GET", params={ "filter": filter, @@ -916,9 +2606,9 @@ async def list_facets( try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicFacetList, + TopicRuleList, parse_obj_as( - type_=TopicFacetList, # type: ignore + type_=TopicRuleList, # type: ignore object_=_response.json(), ), ) @@ -1068,36 +2758,36 @@ async def list_facets( body=_response_json, ) - async def get_facet( - self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[TopicFacet]: + async def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TopicRule]: """ Unstable: Topics is in closed beta. The shape may change without notice. - Get one Topics facet by id, including inactive facets. + Get one Topics rule by id. Parameters ---------- - facet_id : str + rule_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TopicFacet] + AsyncHttpResponse[TopicRule] """ _response = await self._client_wrapper.httpx_client.request( - f"api/public/unstable/topic-facets/{jsonable_encoder(facet_id)}", + f"api/public/unstable/topic-rules/{jsonable_encoder(rule_id)}", method="GET", request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicFacet, + TopicRule, parse_obj_as( - type_=TopicFacet, # type: ignore + type_=TopicRule, # type: ignore object_=_response.json(), ), ) @@ -1247,33 +2937,55 @@ async def get_facet( body=_response_json, ) - async def list( + async def list_summaries( 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, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TopicList]: + ) -> AsyncHttpResponse[TopicSummaryList]: """ 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. + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. 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`. + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. Parameters ---------- @@ -1284,26 +2996,29 @@ async def list( 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"]}]`. + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"traceId","operator":"any of","value":[""]}]`. - order_by : typing.Optional[TopicsOrderBy] - Defaults to `traceCount.desc`. + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. - page : typing.Optional[int] - 1-based page number. Defaults to `1`. + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. limit : typing.Optional[int] - Maximum number of items per page. Defaults to `50`, at most `100`. + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TopicList] + AsyncHttpResponse[TopicSummaryList] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/unstable/topics", + "api/public/unstable/topic-summaries", method="GET", params={ "fromTimestamp": serialize_datetime(from_timestamp) @@ -1313,18 +3028,19 @@ async def list( if to_timestamp is not None else None, "filter": filter, + "truncateSummaries": truncate_summaries, "orderBy": order_by, - "page": page, "limit": limit, + "cursor": cursor, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicList, + TopicSummaryList, parse_obj_as( - type_=TopicList, # type: ignore + type_=TopicSummaryList, # type: ignore object_=_response.json(), ), ) @@ -1474,40 +3190,79 @@ async def list( body=_response_json, ) - async def get( + async def list_assignments( self, - topic_id: str, *, from_timestamp: typing.Optional[dt.datetime] = None, to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TopicWithStatus]: + ) -> AsyncHttpResponse[TopicAssignmentList]: """ 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`. + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. Parameters ---------- - topic_id : str - from_timestamp : typing.Optional[dt.datetime] - Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. to_timestamp : typing.Optional[dt.datetime] - End of the window used for `traceCount`. Defaults to now. + 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":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TopicWithStatus] + AsyncHttpResponse[TopicAssignmentList] """ _response = await self._client_wrapper.httpx_client.request( - f"api/public/unstable/topics/{jsonable_encoder(topic_id)}", + "api/public/unstable/topic-assignments", method="GET", params={ "fromTimestamp": serialize_datetime(from_timestamp) @@ -1516,15 +3271,19 @@ async def get( "toTimestamp": serialize_datetime(to_timestamp) if to_timestamp is not None else None, + "filter": filter, + "orderBy": order_by, + "limit": limit, + "cursor": cursor, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicWithStatus, + TopicAssignmentList, parse_obj_as( - type_=TopicWithStatus, # type: ignore + type_=TopicAssignmentList, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/unstable/topics/types/__init__.py b/langfuse/api/unstable/topics/types/__init__.py index 4e6e56333..1e389eefa 100644 --- a/langfuse/api/unstable/topics/types/__init__.py +++ b/langfuse/api/unstable/topics/types/__init__.py @@ -7,25 +7,47 @@ if typing.TYPE_CHECKING: from .topic import Topic + from .topic_assignment import TopicAssignment + from .topic_assignment_list import TopicAssignmentList + from .topic_assignment_status import TopicAssignmentStatus + from .topic_assignment_topic import TopicAssignmentTopic 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_result_list_meta import TopicResultListMeta + from .topic_results_order_by import TopicResultsOrderBy + from .topic_rule import TopicRule + from .topic_rule_list import TopicRuleList from .topic_status_counts import TopicStatusCounts + from .topic_summary import TopicSummary + from .topic_summary_list import TopicSummaryList + from .topic_summary_state import TopicSummaryState 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", + "TopicAssignment": ".topic_assignment", + "TopicAssignmentList": ".topic_assignment_list", + "TopicAssignmentStatus": ".topic_assignment_status", + "TopicAssignmentTopic": ".topic_assignment_topic", "TopicFacet": ".topic_facet", "TopicFacetCounts": ".topic_facet_counts", "TopicFacetList": ".topic_facet_list", "TopicFacetPublishedRun": ".topic_facet_published_run", "TopicFacetRef": ".topic_facet_ref", "TopicList": ".topic_list", + "TopicResultListMeta": ".topic_result_list_meta", + "TopicResultsOrderBy": ".topic_results_order_by", + "TopicRule": ".topic_rule", + "TopicRuleList": ".topic_rule_list", "TopicStatusCounts": ".topic_status_counts", + "TopicSummary": ".topic_summary", + "TopicSummaryList": ".topic_summary_list", + "TopicSummaryState": ".topic_summary_state", "TopicWindow": ".topic_window", "TopicWithStatus": ".topic_with_status", "TopicsOrderBy": ".topics_order_by", @@ -61,13 +83,24 @@ def __dir__(): __all__ = [ "Topic", + "TopicAssignment", + "TopicAssignmentList", + "TopicAssignmentStatus", + "TopicAssignmentTopic", "TopicFacet", "TopicFacetCounts", "TopicFacetList", "TopicFacetPublishedRun", "TopicFacetRef", "TopicList", + "TopicResultListMeta", + "TopicResultsOrderBy", + "TopicRule", + "TopicRuleList", "TopicStatusCounts", + "TopicSummary", + "TopicSummaryList", + "TopicSummaryState", "TopicWindow", "TopicWithStatus", "TopicsOrderBy", diff --git a/langfuse/api/unstable/topics/types/topic.py b/langfuse/api/unstable/topics/types/topic.py index e32938810..c1bd49195 100644 --- a/langfuse/api/unstable/topics/types/topic.py +++ b/langfuse/api/unstable/topics/types/topic.py @@ -25,6 +25,13 @@ class Topic(UniversalBaseModel): name: str description: str facet: TopicFacetRef + rule_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="ruleId") + ] = pydantic.Field(default=None) + """ + The rule whose live map has the topic. Null when the project has no rule. + """ + run_id: typing_extensions.Annotated[str, FieldMetadata(alias="runId")] = ( pydantic.Field() ) diff --git a/langfuse/api/unstable/topics/types/topic_assignment.py b/langfuse/api/unstable/topics/types/topic_assignment.py new file mode 100644 index 000000000..b600e36ac --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .topic_assignment_status import TopicAssignmentStatus +from .topic_assignment_topic import TopicAssignmentTopic + + +class TopicAssignment(UniversalBaseModel): + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + timestamp: dt.datetime = pydantic.Field() + """ + Trace start time. + """ + + facet_id: typing_extensions.Annotated[str, FieldMetadata(alias="facetId")] + facet_name: typing_extensions.Annotated[str, FieldMetadata(alias="facetName")] + facet_version: typing_extensions.Annotated[int, FieldMetadata(alias="facetVersion")] + rule_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="ruleId") + ] = pydantic.Field(default=None) + """ + The rule whose map assigned the trace. Null when the project has no rule. + """ + + status: TopicAssignmentStatus + topic: typing.Optional[TopicAssignmentTopic] = pydantic.Field(default=None) + """ + Set when `status` is `assigned`. + """ + + run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="runId") + ] = pydantic.Field(default=None) + """ + The run that assigned the trace. Null for assignments made as traces arrive. + """ + + assigned_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="assignedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_assignment_list.py b/langfuse/api/unstable/topics/types/topic_assignment_list.py new file mode 100644 index 000000000..d5be6c11f --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment_list.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .topic_assignment import TopicAssignment +from .topic_result_list_meta import TopicResultListMeta +from .topic_window import TopicWindow + + +class TopicAssignmentList(UniversalBaseModel): + window: TopicWindow + data: typing.List[TopicAssignment] + meta: TopicResultListMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_assignment_status.py b/langfuse/api/unstable/topics/types/topic_assignment_status.py new file mode 100644 index 000000000..bee431ae3 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment_status.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class TopicAssignmentStatus(enum.StrEnum): + ASSIGNED = "assigned" + OUTLIER = "outlier" + + def visit( + self, + assigned: typing.Callable[[], T_Result], + outlier: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicAssignmentStatus.ASSIGNED: + return assigned() + if self is TopicAssignmentStatus.OUTLIER: + return outlier() diff --git a/langfuse/api/unstable/topics/types/topic_assignment_topic.py b/langfuse/api/unstable/topics/types/topic_assignment_topic.py new file mode 100644 index 000000000..7bb0f54f4 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment_topic.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata + + +class TopicAssignmentTopic(UniversalBaseModel): + id: str = pydantic.Field() + """ + Stable topic id. + """ + + version_id: typing_extensions.Annotated[str, FieldMetadata(alias="versionId")] + name: str + + 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 index 2f3673abf..166373b5e 100644 --- a/langfuse/api/unstable/topics/types/topic_facet.py +++ b/langfuse/api/unstable/topics/types/topic_facet.py @@ -16,7 +16,14 @@ class TopicFacet(UniversalBaseModel): 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. + Whether a Topics rule runs the facet. True for all facets if the project has no rule. + """ + + rule_ids: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="ruleIds") + ] = pydantic.Field() + """ + The rules that run the facet. """ version: int = pydantic.Field() diff --git a/langfuse/api/unstable/topics/types/topic_facet_counts.py b/langfuse/api/unstable/topics/types/topic_facet_counts.py index 0bc83e2cf..4932663b6 100644 --- a/langfuse/api/unstable/topics/types/topic_facet_counts.py +++ b/langfuse/api/unstable/topics/types/topic_facet_counts.py @@ -12,6 +12,9 @@ class TopicFacetCounts(UniversalBaseModel): facet: TopicFacetRef + rule_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="ruleId") + ] = None published_run_id: typing_extensions.Annotated[ typing.Optional[str], FieldMetadata(alias="publishedRunId") ] = None diff --git a/langfuse/api/unstable/topics/types/topic_result_list_meta.py b/langfuse/api/unstable/topics/types/topic_result_list_meta.py new file mode 100644 index 000000000..c5cdf08de --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_result_list_meta.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class TopicResultListMeta(UniversalBaseModel): + cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Pass as `cursor` to fetch the next page. Null on the last page. + """ + + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Set when the page is shorter than `limit` but more rows may follow. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_results_order_by.py b/langfuse/api/unstable/topics/types/topic_results_order_by.py new file mode 100644 index 000000000..4df738d50 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_results_order_by.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class TopicResultsOrderBy(enum.StrEnum): + TIMESTAMP_DESC = "timestamp.desc" + TIMESTAMP_ASC = "timestamp.asc" + + def visit( + self, + timestamp_desc: typing.Callable[[], T_Result], + timestamp_asc: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicResultsOrderBy.TIMESTAMP_DESC: + return timestamp_desc() + if self is TopicResultsOrderBy.TIMESTAMP_ASC: + return timestamp_asc() diff --git a/langfuse/api/unstable/topics/types/topic_rule.py b/langfuse/api/unstable/topics/types/topic_rule.py new file mode 100644 index 000000000..a0109241e --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_rule.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from ....evaluation_commons.types.evaluation_rule_read_filter import ( + EvaluationRuleReadFilter, +) + + +class TopicRule(UniversalBaseModel): + id: str + name: str + filter: typing.List[EvaluationRuleReadFilter] = pydantic.Field() + """ + Observation filter that selects the rule's traces, returned verbatim. + """ + + sampling: float = pydantic.Field() + """ + Fraction of matching traces that are processed, between `0` and `1`. + """ + + facet_ids: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="facetIds") + ] = pydantic.Field() + """ + Facets the rule runs. + """ + + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_rule_list.py b/langfuse/api/unstable/topics/types/topic_rule_list.py new file mode 100644 index 000000000..e9b0b7f6c --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_rule_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_rule import TopicRule + + +class TopicRuleList(UniversalBaseModel): + data: typing.List[TopicRule] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_summary.py b/langfuse/api/unstable/topics/types/topic_summary.py new file mode 100644 index 000000000..f4bdbdc2a --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_summary.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .topic_summary_state import TopicSummaryState + + +class TopicSummary(UniversalBaseModel): + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + trace_name: typing_extensions.Annotated[str, FieldMetadata(alias="traceName")] + environment: str + timestamp: dt.datetime = pydantic.Field() + """ + Trace start time. + """ + + facet_id: typing_extensions.Annotated[str, FieldMetadata(alias="facetId")] + facet_name: typing_extensions.Annotated[str, FieldMetadata(alias="facetName")] + facet_version: typing_extensions.Annotated[int, FieldMetadata(alias="facetVersion")] + state: TopicSummaryState + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Set when `state` is `complete`. + """ + + summary_truncated: typing_extensions.Annotated[ + bool, FieldMetadata(alias="summaryTruncated") + ] + processed_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="processedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_summary_list.py b/langfuse/api/unstable/topics/types/topic_summary_list.py new file mode 100644 index 000000000..60a3611dc --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_summary_list.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .topic_result_list_meta import TopicResultListMeta +from .topic_summary import TopicSummary +from .topic_window import TopicWindow + + +class TopicSummaryList(UniversalBaseModel): + window: TopicWindow + data: typing.List[TopicSummary] + meta: TopicResultListMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_summary_state.py b/langfuse/api/unstable/topics/types/topic_summary_state.py new file mode 100644 index 000000000..8b247353b --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_summary_state.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class TopicSummaryState(enum.StrEnum): + COMPLETE = "complete" + NOT_APPLICABLE = "not_applicable" + INSUFFICIENT_INPUT = "insufficient_input" + + def visit( + self, + complete: typing.Callable[[], T_Result], + not_applicable: typing.Callable[[], T_Result], + insufficient_input: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicSummaryState.COMPLETE: + return complete() + if self is TopicSummaryState.NOT_APPLICABLE: + return not_applicable() + if self is TopicSummaryState.INSUFFICIENT_INPUT: + return insufficient_input()