diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index d0e9716..82c8867 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -14,6 +14,7 @@ docs/CategoryPatchBody.md docs/Content.md docs/Content1.md docs/ContentItem.md +docs/ContextInput.md docs/Data.md docs/Data1.md docs/DeleteByIdsData.md @@ -169,6 +170,7 @@ everos_cloud/models/category_patch_body.py everos_cloud/models/content.py everos_cloud/models/content1.py everos_cloud/models/content_item.py +everos_cloud/models/context_input.py everos_cloud/models/data.py everos_cloud/models/data1.py everos_cloud/models/delete_by_ids_data.py @@ -317,6 +319,7 @@ test/test_category_patch_body.py test/test_content.py test/test_content1.py test/test_content_item.py +test/test_context_input.py test/test_data.py test/test_data1.py test/test_delete_by_ids_data.py diff --git a/docs/ContextInput.md b/docs/ContextInput.md new file mode 100644 index 0000000..510582b --- /dev/null +++ b/docs/ContextInput.md @@ -0,0 +1,39 @@ +# ContextInput + +Request body of `POST /api/v2/memory/context`. The same fields as the `/search` request, except that `method` is fixed to `\"context\"`, `min_score` is not supported, and `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**app_id** | **str** | Scope to search in, defaulting to \"default\". Must match the pair used on write. | [optional] [default to 'default'] +**project_id** | **str** | Second half of the scope, defaulting to \"default\". | [optional] [default to 'default'] +**user_id** | **str** | | [optional] +**agent_id** | **str** | | [optional] +**query** | **str** | The question to retrieve for. The loop keeps searching until it judges the evidence sufficient to answer it, so pass the whole question, not keywords. | +**method** | **str** | Fixed to `\"context\"`; may be omitted. Any other value is a 422. | [optional] [default to 'context'] +**top_k** | **int** | Maximum number of episodes returned. Either -1 (the default, letting the engine decide) or a value from 1 to 100; anything else is rejected with 422. | [optional] [default to -1] +**min_score** | **float** | | [optional] +**include_profile** | **bool** | Also return the user's profile alongside the episodes, as on /search. | [optional] [default to False] +**filters** | [**FilterNode**](FilterNode.md) | | [optional] + +## Example + +```python +from everos_cloud.models.context_input import ContextInput + +# TODO update the JSON string below +json = "{}" +# create an instance of ContextInput from a JSON string +context_input_instance = ContextInput.from_json(json) +# print the JSON string representation of the object +print(ContextInput.to_json()) + +# convert the object into a dict +context_input_dict = context_input_instance.to_dict() +# create an instance of ContextInput from a dict +context_input_from_dict = ContextInput.from_dict(context_input_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/EpisodeItem.md b/docs/EpisodeItem.md index e795462..3fd5e8f 100644 --- a/docs/EpisodeItem.md +++ b/docs/EpisodeItem.md @@ -15,12 +15,12 @@ Name | Type | Description | Notes **summary** | **str** | Short summary of the episode — what a result list should show. | **subject** | **str** | What the episode is about, in a few words. | **episode** | **str** | The episode's stored narrative body. This is the indexed, searchable text. | -**readable_episode** | **str** | | [optional] **type** | **str** | How the episode was produced — \"Conversation\" or \"AgentConversation\". | **atomic_facts** | [**List[AtomicFactItem]**](AtomicFactItem.md) | The individual facts extracted from this episode, nested rather than returned separately. | [optional] **tags** | **List[str]** | Tags attached through /api/v2/memory/tag/*. | [optional] **edited_at** | **datetime** | | [optional] **reflect_state** | **str** | | [optional] +**readable_episode** | **str** | | [optional] ## Example diff --git a/docs/GetInput.md b/docs/GetInput.md index 877bae4..ff889f4 100644 --- a/docs/GetInput.md +++ b/docs/GetInput.md @@ -14,7 +14,7 @@ Name | Type | Description | Notes **page_size** | **int** | Items per page, 1 to 100 (default 20). | [optional] [default to 20] **sort_by** | **str** | Order by \"timestamp\" (when the memory happened, default) or \"updated_at\" (when it was last written). Profiles and agent skills have no temporal column and always sort by \"updated_at\". | [optional] [default to 'timestamp'] **sort_order** | **str** | \"desc\" (default, newest first) or \"asc\". | [optional] [default to 'desc'] -**with_readable_episode** | **bool** | Attach a human-readable rendering to each returned episode, for display only. Ignored for every non-episode `memory_type` rather than rejected. | [optional] [default to False] +**with_readable_episode** | **bool** | Deprecated and has no effect. Still accepted so existing clients that send it keep working; new code should leave it out. | [optional] [default to False] **filters** | [**FilterNode**](FilterNode.md) | | [optional] ## Example diff --git a/docs/MemoryApi.md b/docs/MemoryApi.md index 4c75ad1..46227be 100644 --- a/docs/MemoryApi.md +++ b/docs/MemoryApi.md @@ -12,6 +12,7 @@ Method | HTTP request | Description [**flush_memory**](MemoryApi.md#flush_memory) | **POST** /api/v2/memory/flush | Force memory extraction [OSS + Cloud] [**get_memory**](MemoryApi.md#get_memory) | **POST** /api/v2/memory/get | Get memories [OSS + Cloud] [**replace_tags**](MemoryApi.md#replace_tags) | **POST** /api/v2/memory/tag/replace | Replace tags on memories [Cloud] +[**retrieve_context**](MemoryApi.md#retrieve_context) | **POST** /api/v2/memory/context | Context Retrieval [Cloud-only] [**search_memory**](MemoryApi.md#search_memory) | **POST** /api/v2/memory/search | Search memories [OSS + Cloud] [**submit_feedback**](MemoryApi.md#submit_feedback) | **POST** /api/v2/memory/feedback | Submit feedback about one memory [Cloud-only] [**unbind_tags**](MemoryApi.md#unbind_tags) | **POST** /api/v2/memory/tag/unbind | Unbind tags from memories [Cloud] @@ -691,6 +692,90 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **retrieve_context** +> SuccessEnvelopeSearchData retrieve_context(context_input) + +Context Retrieval [Cloud-only] + +Retrieve memories with a multi-round, LLM-driven search loop. An LLM plans and runs several searches over the owner's memories — keyword, semantic, hybrid, topic-cluster, and reading the surrounding conversation or a whole session — and keeps searching until it judges the evidence sufficient to answer `query`. The memories it found are returned. **The response contains memories, not an answer**: the model's own answer is discarded. Use it for questions a single search answers poorly: multi-hop, temporal, or spread across several conversations. For simple lookups, `/search` is faster and cheaper. **Request** — the same body as `/search`, except: - `method` is fixed to `\"context\"`. It can be omitted; any other value is a 422. - `min_score` is not supported (422): results are ranked by rank fusion, not by a similarity score. - `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted (422): the loop does not use them. **Response** — the same `SearchData` shape as `/search`: - The loop returns its memories in `episodes`, for a `user_id` owner only; for an `agent_id` owner `episodes`, `agent_cases` and `agent_skills` are empty. `profiles` (`include_profile`) and `unprocessed_messages` (a top-level `session_id` filter) are filled exactly as on `/search`. - `episodes[].cited` is `true` for memories the model named as the ones its answer rested on. - `episodes[].score` is a rank-fusion score, not a similarity: roughly 0.01–0.05, plus 1.0 for a cited memory, so cited memories come first. It is not comparable with `/search` scores. **Limits** — by default at most 5 LLM rounds and 45 seconds per request. Reaching a limit is not an error: the memories found so far are returned. **Latency** — several LLM calls per request, so noticeably slower than `/search`. + +### Example + +* Bearer Authentication (BearerAuth): + +```python +import everos_cloud +from everos_cloud.models.context_input import ContextInput +from everos_cloud.models.success_envelope_search_data import SuccessEnvelopeSearchData +from everos_cloud.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://api.evermind.ai +# See configuration.py for a list of all supported configuration parameters. +configuration = everos_cloud.Configuration( + host = "https://api.evermind.ai" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: BearerAuth +configuration = everos_cloud.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with everos_cloud.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = everos_cloud.MemoryApi(api_client) + context_input = everos_cloud.ContextInput() # ContextInput | + + try: + # Context Retrieval [Cloud-only] + api_response = api_instance.retrieve_context(context_input) + print("The response of MemoryApi->retrieve_context:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling MemoryApi->retrieve_context: %s\n" % e) +``` + + + +### Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **context_input** | [**ContextInput**](ContextInput.md)| | + +### Return type + +[**SuccessEnvelopeSearchData**](SuccessEnvelopeSearchData.md) + +### Authorization + +[BearerAuth](../README.md#BearerAuth) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | The memories the loop found, in `episodes`, using the same `SearchData` shape as `/search`. | - | +**422** | Validation Error | - | +**401** | Missing or invalid bearer token. | - | +**403** | Authenticated but not permitted — either rejected by the auth service, or the account's memory API version does not match the interface version implied by the path (a v1 account calling an /api/v2 route). | - | +**429** | Rate limit or quota exceeded. | - | +**503** | The gateway could not reach the authentication service. Transient — retry with backoff. | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **search_memory** > SuccessEnvelopeSearchData search_memory(search_input) diff --git a/docs/Reason.md b/docs/Reason.md index 700cb12..4063b3d 100644 --- a/docs/Reason.md +++ b/docs/Reason.md @@ -6,7 +6,7 @@ Why the operator made the change. OPTIONAL on both ops: it is recorded on the au Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**code** | **str** | What was wrong, from the fixed list — hallucination, wrong_subject, wrong_time, redundant, missing, privacy, style, other. Required whenever a reason is given. Same base list as `FeedbackInput.reason` plus `missing`, which only an edit can express (an operator fixes a record that left something out; a rating always names a memory that exists). On the contract this SDK is generated from (engine release-20260901_v15) `outdated` is not yet accepted here — the engine adds it in its next release; until then record an outdated-type fix as `wrong_time` or `other`. | +**code** | **str** | What was wrong, from the fixed list — hallucination, outdated, wrong_subject, wrong_time, redundant, missing, privacy, style, other. Required whenever a reason is given. Same base list as `FeedbackInput.reason` plus `missing`, which only an edit can express (an operator fixes a record that left something out; a rating always names a memory that exists). | **note** | **str** | | [optional] ## Example diff --git a/docs/SearchEpisodeItem.md b/docs/SearchEpisodeItem.md index a8bedfa..e316d2e 100644 --- a/docs/SearchEpisodeItem.md +++ b/docs/SearchEpisodeItem.md @@ -15,13 +15,14 @@ Name | Type | Description | Notes **summary** | **str** | Short summary of the episode — what a result list should show. | **subject** | **str** | What the episode is about, in a few words. | **episode** | **str** | The episode's stored narrative body. This is the indexed, searchable text. | -**readable_episode** | **str** | | [optional] **type** | **str** | How the episode was produced — \"Conversation\" or \"AgentConversation\". | **atomic_facts** | [**List[SearchAtomicFactItem]**](SearchAtomicFactItem.md) | The facts extracted from this episode, each with its own relevance score. | [optional] **tags** | **List[str]** | Tags attached through /api/v2/memory/tag/*. | [optional] **edited_at** | **datetime** | | [optional] **reflect_state** | **str** | | [optional] -**score** | **float** | Relevance of this episode to the query. What the number means depends on `method`: the hybrid path fuses its two routes into a probability in 0.0–1.0 (which is what `min_score` filters on), while keyword and vector pass the underlying engine's own score through — BM25 has no upper bound and vector similarity depends on the metric. So compare scores within one method, not across methods. | +**score** | **float** | Relevance of this episode to the query. What the number means depends on `method`: the hybrid path fuses its two routes into a probability in 0.0–1.0 (which is what `min_score` filters on), while keyword and vector pass the underlying engine's own score through — BM25 has no upper bound and vector similarity depends on the metric. On /api/v2/memory/context it is a rank-fusion score instead, roughly 0.01–0.05, plus 1.0 for a `cited` episode. So compare scores within one method, not across methods. | +**cited** | **bool** | `/context` only: the retrieval model named this memory as one its answer rested on. Always `false` on `/search`. | [optional] [default to False] +**readable_episode** | **str** | | [optional] ## Example diff --git a/docs/SearchInput.md b/docs/SearchInput.md index 6fc0860..346c59e 100644 --- a/docs/SearchInput.md +++ b/docs/SearchInput.md @@ -15,7 +15,7 @@ Name | Type | Description | Notes **radius** | **float** | | [optional] **min_score** | **float** | | [optional] **include_profile** | **bool** | Also return the user's profile alongside the hits, saving a second call. Ignored for an agent owner, whose results carry no profiles. | [optional] [default to False] -**with_readable_episode** | **bool** | Attach a human-readable rendering of each episode to the returned items, for display only — it is not indexed, filterable or scored, and callers fall back to `episode` when it is null. Ignored for an agent owner. | [optional] [default to False] +**with_readable_episode** | **bool** | Deprecated and has no effect. Still accepted so existing clients that send it keep working; new code should leave it out. | [optional] [default to False] **enable_llm_rerank** | **bool** | Opt-in LLM rerank, and only for hybrid agent_case / agent_skill retrieval. The episode hybrid path has its own fact eviction and ignores this, as do keyword, vector and agentic. | [optional] [default to False] **filters** | [**FilterNode**](FilterNode.md) | | [optional] diff --git a/docs/UpdateData.md b/docs/UpdateData.md index 8e24cea..cf3819e 100644 --- a/docs/UpdateData.md +++ b/docs/UpdateData.md @@ -16,13 +16,13 @@ Name | Type | Description | Notes **summary** | **str** | Short summary of the episode — what a result list should show. | **subject** | **str** | What the episode is about, in a few words. | **episode** | **str** | The episode's stored narrative body. This is the indexed, searchable text. | -**readable_episode** | **str** | | [optional] **type** | **str** | How the episode was produced — \"Conversation\" or \"AgentConversation\". | **atomic_facts** | [**List[AtomicFactItem]**](AtomicFactItem.md) | The individual facts extracted from this episode, nested rather than returned separately. | [optional] **tags** | **List[str]** | Tags attached through /api/v2/memory/tag/*. | [optional] **edited_at** | **datetime** | | [optional] **reflect_state** | **str** | | [optional] **unchanged** | **bool** | True when the patch equalled what was already stored — nothing was written and nothing downstream was triggered. | [optional] [default to False] +**readable_episode** | **str** | | [optional] ## Example diff --git a/everos_cloud/__init__.py b/everos_cloud/__init__.py index 3f95b0a..2ad3e33 100644 --- a/everos_cloud/__init__.py +++ b/everos_cloud/__init__.py @@ -15,7 +15,7 @@ """ # noqa: E501 -__version__ = "1.2.0" +__version__ = "1.3.0-rc1" # import apis into sdk package from everos_cloud.api.knowledge_api import KnowledgeApi @@ -49,6 +49,7 @@ from everos_cloud.models.content import Content from everos_cloud.models.content1 import Content1 from everos_cloud.models.content_item import ContentItem +from everos_cloud.models.context_input import ContextInput from everos_cloud.models.data import Data from everos_cloud.models.data1 import Data1 from everos_cloud.models.delete_by_ids_data import DeleteByIdsData diff --git a/everos_cloud/api/memory_api.py b/everos_cloud/api/memory_api.py index a862cbb..9da4c7f 100644 --- a/everos_cloud/api/memory_api.py +++ b/everos_cloud/api/memory_api.py @@ -18,6 +18,7 @@ from typing_extensions import Annotated from everos_cloud.models.add_input import AddInput +from everos_cloud.models.context_input import ContextInput from everos_cloud.models.delete_by_ids_input import DeleteByIdsInput from everos_cloud.models.delete_input import DeleteInput from everos_cloud.models.edit_input import EditInput @@ -2375,6 +2376,295 @@ def _replace_tags_serialize( + @validate_call + def retrieve_context( + self, + context_input: ContextInput, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> SuccessEnvelopeSearchData: + """Context Retrieval [Cloud-only] + + Retrieve memories with a multi-round, LLM-driven search loop. An LLM plans and runs several searches over the owner's memories — keyword, semantic, hybrid, topic-cluster, and reading the surrounding conversation or a whole session — and keeps searching until it judges the evidence sufficient to answer `query`. The memories it found are returned. **The response contains memories, not an answer**: the model's own answer is discarded. Use it for questions a single search answers poorly: multi-hop, temporal, or spread across several conversations. For simple lookups, `/search` is faster and cheaper. **Request** — the same body as `/search`, except: - `method` is fixed to `\"context\"`. It can be omitted; any other value is a 422. - `min_score` is not supported (422): results are ranked by rank fusion, not by a similarity score. - `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted (422): the loop does not use them. **Response** — the same `SearchData` shape as `/search`: - The loop returns its memories in `episodes`, for a `user_id` owner only; for an `agent_id` owner `episodes`, `agent_cases` and `agent_skills` are empty. `profiles` (`include_profile`) and `unprocessed_messages` (a top-level `session_id` filter) are filled exactly as on `/search`. - `episodes[].cited` is `true` for memories the model named as the ones its answer rested on. - `episodes[].score` is a rank-fusion score, not a similarity: roughly 0.01–0.05, plus 1.0 for a cited memory, so cited memories come first. It is not comparable with `/search` scores. **Limits** — by default at most 5 LLM rounds and 45 seconds per request. Reaching a limit is not an error: the memories found so far are returned. **Latency** — several LLM calls per request, so noticeably slower than `/search`. + + :param context_input: (required) + :type context_input: ContextInput + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._retrieve_context_serialize( + context_input=context_input, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "SuccessEnvelopeSearchData", + '422': "HTTPValidationError", + '401': "Dict[str, object]", + '403': "Dict[str, object]", + '429': "Dict[str, object]", + '503': "Dict[str, object]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def retrieve_context_with_http_info( + self, + context_input: ContextInput, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[SuccessEnvelopeSearchData]: + """Context Retrieval [Cloud-only] + + Retrieve memories with a multi-round, LLM-driven search loop. An LLM plans and runs several searches over the owner's memories — keyword, semantic, hybrid, topic-cluster, and reading the surrounding conversation or a whole session — and keeps searching until it judges the evidence sufficient to answer `query`. The memories it found are returned. **The response contains memories, not an answer**: the model's own answer is discarded. Use it for questions a single search answers poorly: multi-hop, temporal, or spread across several conversations. For simple lookups, `/search` is faster and cheaper. **Request** — the same body as `/search`, except: - `method` is fixed to `\"context\"`. It can be omitted; any other value is a 422. - `min_score` is not supported (422): results are ranked by rank fusion, not by a similarity score. - `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted (422): the loop does not use them. **Response** — the same `SearchData` shape as `/search`: - The loop returns its memories in `episodes`, for a `user_id` owner only; for an `agent_id` owner `episodes`, `agent_cases` and `agent_skills` are empty. `profiles` (`include_profile`) and `unprocessed_messages` (a top-level `session_id` filter) are filled exactly as on `/search`. - `episodes[].cited` is `true` for memories the model named as the ones its answer rested on. - `episodes[].score` is a rank-fusion score, not a similarity: roughly 0.01–0.05, plus 1.0 for a cited memory, so cited memories come first. It is not comparable with `/search` scores. **Limits** — by default at most 5 LLM rounds and 45 seconds per request. Reaching a limit is not an error: the memories found so far are returned. **Latency** — several LLM calls per request, so noticeably slower than `/search`. + + :param context_input: (required) + :type context_input: ContextInput + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._retrieve_context_serialize( + context_input=context_input, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "SuccessEnvelopeSearchData", + '422': "HTTPValidationError", + '401': "Dict[str, object]", + '403': "Dict[str, object]", + '429': "Dict[str, object]", + '503': "Dict[str, object]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def retrieve_context_without_preload_content( + self, + context_input: ContextInput, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Context Retrieval [Cloud-only] + + Retrieve memories with a multi-round, LLM-driven search loop. An LLM plans and runs several searches over the owner's memories — keyword, semantic, hybrid, topic-cluster, and reading the surrounding conversation or a whole session — and keeps searching until it judges the evidence sufficient to answer `query`. The memories it found are returned. **The response contains memories, not an answer**: the model's own answer is discarded. Use it for questions a single search answers poorly: multi-hop, temporal, or spread across several conversations. For simple lookups, `/search` is faster and cheaper. **Request** — the same body as `/search`, except: - `method` is fixed to `\"context\"`. It can be omitted; any other value is a 422. - `min_score` is not supported (422): results are ranked by rank fusion, not by a similarity score. - `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted (422): the loop does not use them. **Response** — the same `SearchData` shape as `/search`: - The loop returns its memories in `episodes`, for a `user_id` owner only; for an `agent_id` owner `episodes`, `agent_cases` and `agent_skills` are empty. `profiles` (`include_profile`) and `unprocessed_messages` (a top-level `session_id` filter) are filled exactly as on `/search`. - `episodes[].cited` is `true` for memories the model named as the ones its answer rested on. - `episodes[].score` is a rank-fusion score, not a similarity: roughly 0.01–0.05, plus 1.0 for a cited memory, so cited memories come first. It is not comparable with `/search` scores. **Limits** — by default at most 5 LLM rounds and 45 seconds per request. Reaching a limit is not an error: the memories found so far are returned. **Latency** — several LLM calls per request, so noticeably slower than `/search`. + + :param context_input: (required) + :type context_input: ContextInput + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._retrieve_context_serialize( + context_input=context_input, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "SuccessEnvelopeSearchData", + '422': "HTTPValidationError", + '401': "Dict[str, object]", + '403': "Dict[str, object]", + '429': "Dict[str, object]", + '503': "Dict[str, object]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _retrieve_context_serialize( + self, + context_input, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if context_input is not None: + _body_params = context_input + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/api/v2/memory/context', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + @validate_call def search_memory( self, diff --git a/everos_cloud/api_client.py b/everos_cloud/api_client.py index 20db421..525cd22 100644 --- a/everos_cloud/api_client.py +++ b/everos_cloud/api_client.py @@ -91,7 +91,7 @@ def __init__( self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/1.2.0/python' + self.user_agent = 'OpenAPI-Generator/1.3.0-rc1/python' self.client_side_validation = configuration.client_side_validation def __enter__(self): diff --git a/everos_cloud/configuration.py b/everos_cloud/configuration.py index ec0ec4c..4798feb 100644 --- a/everos_cloud/configuration.py +++ b/everos_cloud/configuration.py @@ -504,7 +504,7 @@ def to_debug_report(self) -> str: "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: 2.0.0\n"\ - "SDK Package Version: 1.2.0".\ + "SDK Package Version: 1.3.0-rc1".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self) -> List[HostSetting]: diff --git a/everos_cloud/models/__init__.py b/everos_cloud/models/__init__.py index 62aa6d4..a53eef7 100644 --- a/everos_cloud/models/__init__.py +++ b/everos_cloud/models/__init__.py @@ -29,6 +29,7 @@ from everos_cloud.models.content import Content from everos_cloud.models.content1 import Content1 from everos_cloud.models.content_item import ContentItem +from everos_cloud.models.context_input import ContextInput from everos_cloud.models.data import Data from everos_cloud.models.data1 import Data1 from everos_cloud.models.delete_by_ids_data import DeleteByIdsData diff --git a/everos_cloud/models/context_input.py b/everos_cloud/models/context_input.py new file mode 100644 index 0000000..5a13c36 --- /dev/null +++ b/everos_cloud/models/context_input.py @@ -0,0 +1,154 @@ +# coding: utf-8 + +""" + EverOS Cloud Memory API + + Official Python client for the EverOS Cloud Memory API. Add, search, retrieve, and manage long-term memory for your AI applications over a typed interface (pydantic v2, with full type hints). Install and usage guides: https://github.com/EverMind-AI/everos-cloud-sdk-python + + The version of the OpenAPI document: 2.0.0 + Contact: service@evermind.ai + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional, Union +from typing_extensions import Annotated +from everos_cloud.models.filter_node import FilterNode +from typing import Optional, Set +from typing_extensions import Self + +class ContextInput(BaseModel): + """ + Request body of `POST /api/v2/memory/context`. The same fields as the `/search` request, except that `method` is fixed to `\"context\"`, `min_score` is not supported, and `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted. + """ # noqa: E501 + app_id: Optional[Annotated[str, Field(strict=True, max_length=128)]] = Field(default='default', description="Scope to search in, defaulting to \"default\". Must match the pair used on write.") + project_id: Optional[Annotated[str, Field(strict=True, max_length=128)]] = Field(default='default', description="Second half of the scope, defaulting to \"default\".") + user_id: Optional[Annotated[str, Field(min_length=1, strict=True, max_length=256)]] = None + agent_id: Optional[Annotated[str, Field(min_length=1, strict=True, max_length=256)]] = None + query: Annotated[str, Field(min_length=1, strict=True)] = Field(description="The question to retrieve for. The loop keeps searching until it judges the evidence sufficient to answer it, so pass the whole question, not keywords.") + method: Optional[StrictStr] = Field(default='context', description="Fixed to `\"context\"`; may be omitted. Any other value is a 422.") + top_k: Optional[StrictInt] = Field(default=-1, description="Maximum number of episodes returned. Either -1 (the default, letting the engine decide) or a value from 1 to 100; anything else is rejected with 422.") + min_score: Optional[Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]]] = None + include_profile: Optional[StrictBool] = Field(default=False, description="Also return the user's profile alongside the episodes, as on /search.") + filters: Optional[FilterNode] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["app_id", "project_id", "user_id", "agent_id", "query", "method", "top_k", "min_score", "include_profile", "filters"] + + @field_validator('method') + def method_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['context']): + raise ValueError("must be one of enum values ('context')") + return value + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ContextInput from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of filters + if self.filters: + _dict['filters'] = self.filters.to_dict() + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + # set to None if user_id (nullable) is None + # and model_fields_set contains the field + if self.user_id is None and "user_id" in self.model_fields_set: + _dict['user_id'] = None + + # set to None if agent_id (nullable) is None + # and model_fields_set contains the field + if self.agent_id is None and "agent_id" in self.model_fields_set: + _dict['agent_id'] = None + + # set to None if min_score (nullable) is None + # and model_fields_set contains the field + if self.min_score is None and "min_score" in self.model_fields_set: + _dict['min_score'] = None + + # set to None if filters (nullable) is None + # and model_fields_set contains the field + if self.filters is None and "filters" in self.model_fields_set: + _dict['filters'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ContextInput from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "app_id": obj.get("app_id") if obj.get("app_id") is not None else 'default', + "project_id": obj.get("project_id") if obj.get("project_id") is not None else 'default', + "user_id": obj.get("user_id"), + "agent_id": obj.get("agent_id"), + "query": obj.get("query"), + "method": obj.get("method") if obj.get("method") is not None else 'context', + "top_k": obj.get("top_k") if obj.get("top_k") is not None else -1, + "min_score": obj.get("min_score"), + "include_profile": obj.get("include_profile") if obj.get("include_profile") is not None else False, + "filters": FilterNode.from_dict(obj["filters"]) if obj.get("filters") is not None else None + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/everos_cloud/models/episode_item.py b/everos_cloud/models/episode_item.py index 381e6d6..ab181e2 100644 --- a/everos_cloud/models/episode_item.py +++ b/everos_cloud/models/episode_item.py @@ -39,14 +39,14 @@ class EpisodeItem(BaseModel): summary: StrictStr = Field(description="Short summary of the episode — what a result list should show.") subject: StrictStr = Field(description="What the episode is about, in a few words.") episode: StrictStr = Field(description="The episode's stored narrative body. This is the indexed, searchable text.") - readable_episode: Optional[StrictStr] = None type: StrictStr = Field(description="How the episode was produced — \"Conversation\" or \"AgentConversation\".") atomic_facts: Optional[List[AtomicFactItem]] = Field(default=None, description="The individual facts extracted from this episode, nested rather than returned separately.") tags: Optional[List[StrictStr]] = Field(default=None, description="Tags attached through /api/v2/memory/tag/*.") edited_at: Optional[datetime] = None reflect_state: Optional[StrictStr] = None + readable_episode: Optional[StrictStr] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "app_id", "project_id", "user_id", "session_id", "timestamp", "sender_ids", "summary", "subject", "episode", "readable_episode", "type", "atomic_facts", "tags", "edited_at", "reflect_state"] + __properties: ClassVar[List[str]] = ["id", "app_id", "project_id", "user_id", "session_id", "timestamp", "sender_ids", "summary", "subject", "episode", "type", "atomic_facts", "tags", "edited_at", "reflect_state", "readable_episode"] @field_validator('reflect_state') def reflect_state_validate_enum(cls, value): @@ -121,11 +121,6 @@ def to_dict(self) -> Dict[str, Any]: if self.session_id is None and "session_id" in self.model_fields_set: _dict['session_id'] = None - # set to None if readable_episode (nullable) is None - # and model_fields_set contains the field - if self.readable_episode is None and "readable_episode" in self.model_fields_set: - _dict['readable_episode'] = None - # set to None if edited_at (nullable) is None # and model_fields_set contains the field if self.edited_at is None and "edited_at" in self.model_fields_set: @@ -136,6 +131,11 @@ def to_dict(self) -> Dict[str, Any]: if self.reflect_state is None and "reflect_state" in self.model_fields_set: _dict['reflect_state'] = None + # set to None if readable_episode (nullable) is None + # and model_fields_set contains the field + if self.readable_episode is None and "readable_episode" in self.model_fields_set: + _dict['readable_episode'] = None + return _dict @classmethod @@ -158,12 +158,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "summary": obj.get("summary"), "subject": obj.get("subject"), "episode": obj.get("episode"), - "readable_episode": obj.get("readable_episode"), "type": obj.get("type"), "atomic_facts": [AtomicFactItem.from_dict(_item) for _item in obj["atomic_facts"]] if obj.get("atomic_facts") is not None else None, "tags": obj.get("tags"), "edited_at": obj.get("edited_at"), - "reflect_state": obj.get("reflect_state") + "reflect_state": obj.get("reflect_state"), + "readable_episode": obj.get("readable_episode") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/everos_cloud/models/get_input.py b/everos_cloud/models/get_input.py index 7852e76..9c9b4a5 100644 --- a/everos_cloud/models/get_input.py +++ b/everos_cloud/models/get_input.py @@ -38,7 +38,7 @@ class GetInput(BaseModel): page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=1)]] = Field(default=20, description="Items per page, 1 to 100 (default 20).") sort_by: Optional[StrictStr] = Field(default='timestamp', description="Order by \"timestamp\" (when the memory happened, default) or \"updated_at\" (when it was last written). Profiles and agent skills have no temporal column and always sort by \"updated_at\".") sort_order: Optional[StrictStr] = Field(default='desc', description="\"desc\" (default, newest first) or \"asc\".") - with_readable_episode: Optional[StrictBool] = Field(default=False, description="Attach a human-readable rendering to each returned episode, for display only. Ignored for every non-episode `memory_type` rather than rejected.") + with_readable_episode: Optional[StrictBool] = Field(default=False, description="Deprecated and has no effect. Still accepted so existing clients that send it keep working; new code should leave it out.") filters: Optional[FilterNode] = None additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["app_id", "project_id", "user_id", "agent_id", "memory_type", "page", "page_size", "sort_by", "sort_order", "with_readable_episode", "filters"] diff --git a/everos_cloud/models/reason.py b/everos_cloud/models/reason.py index 1cebd97..b3b1ac5 100644 --- a/everos_cloud/models/reason.py +++ b/everos_cloud/models/reason.py @@ -28,7 +28,7 @@ class Reason(BaseModel): """ Why the operator made the change. OPTIONAL on both ops: it is recorded on the audit event for quality analysis, never used to gate anything. When given, ``code`` is required and ``note`` is free text. """ # noqa: E501 - code: StrictStr = Field(description="What was wrong, from the fixed list — hallucination, wrong_subject, wrong_time, redundant, missing, privacy, style, other. Required whenever a reason is given. Same base list as `FeedbackInput.reason` plus `missing`, which only an edit can express (an operator fixes a record that left something out; a rating always names a memory that exists). On the contract this SDK is generated from (engine release-20260901_v15) `outdated` is not yet accepted here — the engine adds it in its next release; until then record an outdated-type fix as `wrong_time` or `other`.") + code: StrictStr = Field(description="What was wrong, from the fixed list — hallucination, outdated, wrong_subject, wrong_time, redundant, missing, privacy, style, other. Required whenever a reason is given. Same base list as `FeedbackInput.reason` plus `missing`, which only an edit can express (an operator fixes a record that left something out; a rating always names a memory that exists).") note: Optional[Annotated[str, Field(strict=True, max_length=256)]] = None additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["code", "note"] @@ -36,8 +36,8 @@ class Reason(BaseModel): @field_validator('code') def code_validate_enum(cls, value): """Validates the enum""" - if value not in set(['hallucination', 'wrong_subject', 'wrong_time', 'redundant', 'missing', 'privacy', 'style', 'other']): - raise ValueError("must be one of enum values ('hallucination', 'wrong_subject', 'wrong_time', 'redundant', 'missing', 'privacy', 'style', 'other')") + if value not in set(['hallucination', 'outdated', 'wrong_subject', 'wrong_time', 'redundant', 'missing', 'privacy', 'style', 'other']): + raise ValueError("must be one of enum values ('hallucination', 'outdated', 'wrong_subject', 'wrong_time', 'redundant', 'missing', 'privacy', 'style', 'other')") return value model_config = ConfigDict( diff --git a/everos_cloud/models/search_episode_item.py b/everos_cloud/models/search_episode_item.py index 6924cf2..e8d1411 100644 --- a/everos_cloud/models/search_episode_item.py +++ b/everos_cloud/models/search_episode_item.py @@ -19,7 +19,7 @@ import json from datetime import datetime -from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, field_validator from typing import Any, ClassVar, Dict, List, Optional, Union from everos_cloud.models.search_atomic_fact_item import SearchAtomicFactItem from typing import Optional, Set @@ -39,15 +39,16 @@ class SearchEpisodeItem(BaseModel): summary: StrictStr = Field(description="Short summary of the episode — what a result list should show.") subject: StrictStr = Field(description="What the episode is about, in a few words.") episode: StrictStr = Field(description="The episode's stored narrative body. This is the indexed, searchable text.") - readable_episode: Optional[StrictStr] = None type: StrictStr = Field(description="How the episode was produced — \"Conversation\" or \"AgentConversation\".") atomic_facts: Optional[List[SearchAtomicFactItem]] = Field(default=None, description="The facts extracted from this episode, each with its own relevance score.") tags: Optional[List[StrictStr]] = Field(default=None, description="Tags attached through /api/v2/memory/tag/*.") edited_at: Optional[datetime] = None reflect_state: Optional[StrictStr] = None - score: Union[StrictFloat, StrictInt] = Field(description="Relevance of this episode to the query. What the number means depends on `method`: the hybrid path fuses its two routes into a probability in 0.0–1.0 (which is what `min_score` filters on), while keyword and vector pass the underlying engine's own score through — BM25 has no upper bound and vector similarity depends on the metric. So compare scores within one method, not across methods.") + score: Union[StrictFloat, StrictInt] = Field(description="Relevance of this episode to the query. What the number means depends on `method`: the hybrid path fuses its two routes into a probability in 0.0–1.0 (which is what `min_score` filters on), while keyword and vector pass the underlying engine's own score through — BM25 has no upper bound and vector similarity depends on the metric. On /api/v2/memory/context it is a rank-fusion score instead, roughly 0.01–0.05, plus 1.0 for a `cited` episode. So compare scores within one method, not across methods.") + cited: Optional[StrictBool] = Field(default=False, description="`/context` only: the retrieval model named this memory as one its answer rested on. Always `false` on `/search`.") + readable_episode: Optional[StrictStr] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "app_id", "project_id", "user_id", "session_id", "timestamp", "sender_ids", "summary", "subject", "episode", "readable_episode", "type", "atomic_facts", "tags", "edited_at", "reflect_state", "score"] + __properties: ClassVar[List[str]] = ["id", "app_id", "project_id", "user_id", "session_id", "timestamp", "sender_ids", "summary", "subject", "episode", "type", "atomic_facts", "tags", "edited_at", "reflect_state", "score", "cited", "readable_episode"] @field_validator('reflect_state') def reflect_state_validate_enum(cls, value): @@ -122,11 +123,6 @@ def to_dict(self) -> Dict[str, Any]: if self.session_id is None and "session_id" in self.model_fields_set: _dict['session_id'] = None - # set to None if readable_episode (nullable) is None - # and model_fields_set contains the field - if self.readable_episode is None and "readable_episode" in self.model_fields_set: - _dict['readable_episode'] = None - # set to None if edited_at (nullable) is None # and model_fields_set contains the field if self.edited_at is None and "edited_at" in self.model_fields_set: @@ -137,6 +133,11 @@ def to_dict(self) -> Dict[str, Any]: if self.reflect_state is None and "reflect_state" in self.model_fields_set: _dict['reflect_state'] = None + # set to None if readable_episode (nullable) is None + # and model_fields_set contains the field + if self.readable_episode is None and "readable_episode" in self.model_fields_set: + _dict['readable_episode'] = None + return _dict @classmethod @@ -159,13 +160,14 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "summary": obj.get("summary"), "subject": obj.get("subject"), "episode": obj.get("episode"), - "readable_episode": obj.get("readable_episode"), "type": obj.get("type"), "atomic_facts": [SearchAtomicFactItem.from_dict(_item) for _item in obj["atomic_facts"]] if obj.get("atomic_facts") is not None else None, "tags": obj.get("tags"), "edited_at": obj.get("edited_at"), "reflect_state": obj.get("reflect_state"), - "score": obj.get("score") + "score": obj.get("score"), + "cited": obj.get("cited") if obj.get("cited") is not None else False, + "readable_episode": obj.get("readable_episode") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/everos_cloud/models/search_input.py b/everos_cloud/models/search_input.py index 70a5dfd..d699fa6 100644 --- a/everos_cloud/models/search_input.py +++ b/everos_cloud/models/search_input.py @@ -39,7 +39,7 @@ class SearchInput(BaseModel): radius: Optional[Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]]] = None min_score: Optional[Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]]] = None include_profile: Optional[StrictBool] = Field(default=False, description="Also return the user's profile alongside the hits, saving a second call. Ignored for an agent owner, whose results carry no profiles.") - with_readable_episode: Optional[StrictBool] = Field(default=False, description="Attach a human-readable rendering of each episode to the returned items, for display only — it is not indexed, filterable or scored, and callers fall back to `episode` when it is null. Ignored for an agent owner.") + with_readable_episode: Optional[StrictBool] = Field(default=False, description="Deprecated and has no effect. Still accepted so existing clients that send it keep working; new code should leave it out.") enable_llm_rerank: Optional[StrictBool] = Field(default=False, description="Opt-in LLM rerank, and only for hybrid agent_case / agent_skill retrieval. The episode hybrid path has its own fact eviction and ignores this, as do keyword, vector and agentic.") filters: Optional[FilterNode] = None additional_properties: Dict[str, Any] = {} diff --git a/everos_cloud/models/update_data.py b/everos_cloud/models/update_data.py index 36044e0..f2dcdf6 100644 --- a/everos_cloud/models/update_data.py +++ b/everos_cloud/models/update_data.py @@ -39,15 +39,15 @@ class UpdateData(BaseModel): summary: StrictStr = Field(description="Short summary of the episode — what a result list should show.") subject: StrictStr = Field(description="What the episode is about, in a few words.") episode: StrictStr = Field(description="The episode's stored narrative body. This is the indexed, searchable text.") - readable_episode: Optional[StrictStr] = None type: StrictStr = Field(description="How the episode was produced — \"Conversation\" or \"AgentConversation\".") atomic_facts: Optional[List[AtomicFactItem]] = Field(default=None, description="The individual facts extracted from this episode, nested rather than returned separately.") tags: Optional[List[StrictStr]] = Field(default=None, description="Tags attached through /api/v2/memory/tag/*.") edited_at: Optional[datetime] = None reflect_state: Optional[StrictStr] = None unchanged: Optional[StrictBool] = Field(default=False, description="True when the patch equalled what was already stored — nothing was written and nothing downstream was triggered.") + readable_episode: Optional[StrictStr] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "app_id", "project_id", "user_id", "session_id", "timestamp", "sender_ids", "summary", "subject", "episode", "readable_episode", "type", "atomic_facts", "tags", "edited_at", "reflect_state", "unchanged"] + __properties: ClassVar[List[str]] = ["id", "app_id", "project_id", "user_id", "session_id", "timestamp", "sender_ids", "summary", "subject", "episode", "type", "atomic_facts", "tags", "edited_at", "reflect_state", "unchanged", "readable_episode"] @field_validator('reflect_state') def reflect_state_validate_enum(cls, value): @@ -122,11 +122,6 @@ def to_dict(self) -> Dict[str, Any]: if self.session_id is None and "session_id" in self.model_fields_set: _dict['session_id'] = None - # set to None if readable_episode (nullable) is None - # and model_fields_set contains the field - if self.readable_episode is None and "readable_episode" in self.model_fields_set: - _dict['readable_episode'] = None - # set to None if edited_at (nullable) is None # and model_fields_set contains the field if self.edited_at is None and "edited_at" in self.model_fields_set: @@ -137,6 +132,11 @@ def to_dict(self) -> Dict[str, Any]: if self.reflect_state is None and "reflect_state" in self.model_fields_set: _dict['reflect_state'] = None + # set to None if readable_episode (nullable) is None + # and model_fields_set contains the field + if self.readable_episode is None and "readable_episode" in self.model_fields_set: + _dict['readable_episode'] = None + return _dict @classmethod @@ -159,13 +159,13 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "summary": obj.get("summary"), "subject": obj.get("subject"), "episode": obj.get("episode"), - "readable_episode": obj.get("readable_episode"), "type": obj.get("type"), "atomic_facts": [AtomicFactItem.from_dict(_item) for _item in obj["atomic_facts"]] if obj.get("atomic_facts") is not None else None, "tags": obj.get("tags"), "edited_at": obj.get("edited_at"), "reflect_state": obj.get("reflect_state"), - "unchanged": obj.get("unchanged") if obj.get("unchanged") is not None else False + "unchanged": obj.get("unchanged") if obj.get("unchanged") is not None else False, + "readable_episode": obj.get("readable_episode") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/openapi.json b/openapi.json index a2d8571..50d0dc8 100644 --- a/openapi.json +++ b/openapi.json @@ -107,6 +107,88 @@ "description": "Append conversation messages to a session's working memory. One call carries 1\u2013500 messages.\n\nThe write is asynchronous by default (`async_mode` true): the gateway validates and enqueues it, answering 202 with status \"queued\". Pass `async_mode: false` to forward synchronously and receive the engine's 200 result instead.\n\nDistillation into long-term memory is always asynchronous \u2014 it runs on a session boundary, or when you call /api/v2/memory/flush." } }, + "/api/v2/memory/context": { + "post": { + "tags": [ + "Memory" + ], + "summary": "Context Retrieval [Cloud-only]", + "description": "Retrieve memories with a multi-round, LLM-driven search loop.\n\nAn LLM plans and runs several searches over the owner's memories \u2014 keyword, semantic, hybrid, topic-cluster, and reading the surrounding conversation or a whole session \u2014 and keeps searching until it judges the evidence sufficient to answer `query`. The memories it found are returned. **The response contains memories, not an answer**: the model's own answer is discarded.\n\nUse it for questions a single search answers poorly: multi-hop, temporal, or spread across several conversations. For simple lookups, `/search` is faster and cheaper.\n\n**Request** \u2014 the same body as `/search`, except:\n- `method` is fixed to `\"context\"`. It can be omitted; any other value is a 422.\n- `min_score` is not supported (422): results are ranked by rank fusion, not by a similarity score.\n- `radius`, `enable_llm_rerank` and `with_readable_episode` are not accepted (422): the loop does not use them.\n\n**Response** \u2014 the same `SearchData` shape as `/search`:\n- The loop returns its memories in `episodes`, for a `user_id` owner only; for an `agent_id` owner `episodes`, `agent_cases` and `agent_skills` are empty. `profiles` (`include_profile`) and `unprocessed_messages` (a top-level `session_id` filter) are filled exactly as on `/search`.\n- `episodes[].cited` is `true` for memories the model named as the ones its answer rested on.\n- `episodes[].score` is a rank-fusion score, not a similarity: roughly 0.01\u20130.05, plus 1.0 for a cited memory, so cited memories come first. It is not comparable with `/search` scores.\n\n**Limits** \u2014 by default at most 5 LLM rounds and 45 seconds per request. Reaching a limit is not an error: the memories found so far are returned.\n\n**Latency** \u2014 several LLM calls per request, so noticeably slower than `/search`.", + "operationId": "retrieveContext", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContextInput" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The memories the loop found, in `episodes`, using the same `SearchData` shape as `/search`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope_SearchData_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + }, + "401": { + "description": "Missing or invalid bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayError" + } + } + } + }, + "403": { + "description": "Authenticated but not permitted \u2014 either rejected by the auth service, or the account's memory API version does not match the interface version implied by the path (a v1 account calling an /api/v2 route).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayError" + } + } + } + }, + "429": { + "description": "Rate limit or quota exceeded.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayError" + } + } + } + }, + "503": { + "description": "The gateway could not reach the authentication service. Transient \u2014 retry with backoff.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayError" + } + } + } + } + } + } + }, "/api/v2/memory/delete": { "post": { "tags": [ @@ -4052,6 +4134,109 @@ "title": "ContentItem", "description": "A single content element (appendix A). Current phase: only ``type=\"text\"``." }, + "ContextInput": { + "properties": { + "app_id": { + "type": "string", + "maxLength": 128, + "title": "App Id", + "default": "default", + "description": "Scope to search in, defaulting to \"default\". Must match the pair used on write." + }, + "project_id": { + "type": "string", + "maxLength": 128, + "title": "Project Id", + "default": "default", + "description": "Second half of the scope, defaulting to \"default\"." + }, + "user_id": { + "anyOf": [ + { + "type": "string", + "maxLength": 256, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "User Id", + "description": "Search one user's memories \u2014 matched against the messages' `sender_id`. Exactly one of `user_id` / `agent_id` is required, and only a user owner gets episodes back." + }, + "agent_id": { + "anyOf": [ + { + "type": "string", + "maxLength": 256, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Agent Id", + "description": "Accepted for symmetry with /search, but the loop retrieves user episodes only: with an agent owner every result collection is empty." + }, + "query": { + "type": "string", + "minLength": 1, + "title": "Query", + "description": "The question to retrieve for. The loop keeps searching until it judges the evidence sufficient to answer it, so pass the whole question, not keywords." + }, + "method": { + "type": "string", + "const": "context", + "title": "Method", + "description": "Fixed to `\"context\"`; may be omitted. Any other value is a 422.", + "default": "context" + }, + "top_k": { + "type": "integer", + "title": "Top K", + "default": -1, + "description": "Maximum number of episodes returned. Either -1 (the default, letting the engine decide) or a value from 1 to 100; anything else is rejected with 422." + }, + "min_score": { + "anyOf": [ + { + "type": "number", + "maximum": 1.0, + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Min Score", + "description": "Not supported on `/context` \u2014 any value is a 422. Results are ranked by rank fusion, not by a similarity score, so there is no floor to apply." + }, + "include_profile": { + "type": "boolean", + "title": "Include Profile", + "default": false, + "description": "Also return the user's profile alongside the episodes, as on /search." + }, + "filters": { + "anyOf": [ + { + "$ref": "#/components/schemas/FilterNode" + }, + { + "type": "null" + } + ], + "description": "Optional filter tree, same shape as on /search. It is applied to every search the loop runs." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "query" + ], + "title": "ContextInput", + "description": "Request body of `POST /api/v2/memory/context`.\n\nThe same fields as the `/search` request, except that `method` is fixed to\n`\"context\"`, `min_score` is not supported, and `radius`, `enable_llm_rerank` and\n`with_readable_episode` are not accepted." + }, "DeleteByIdsData": { "properties": { "memory_type": { @@ -4845,18 +5030,6 @@ "title": "Episode", "description": "The episode's stored narrative body. This is the indexed, searchable text." }, - "readable_episode": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Readable Episode", - "description": "Human-readable rendering of `episode`, for display only \u2014 never indexed, filtered or scored. Present as a key but null unless the request asked for it (and the tenant is enabled for it); fall back to `episode` when it is null." - }, "type": { "type": "string", "title": "Type", @@ -4906,6 +5079,19 @@ ], "title": "Reflect State", "description": "Set once the narrative has been edited by hand: \"pending\" until background consolidation has absorbed the new text (a search may briefly return both it and a merged narrative close to it), \"done\" afterwards. Absent on a memory whose narrative was never edited." + }, + "readable_episode": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Readable Episode", + "deprecated": true, + "description": "Deprecated and always null. Read `episode` instead." } }, "type": "object", @@ -5150,7 +5336,7 @@ } ], "title": "Reason", - "description": "Structured reason used for analysis; it never triggers mutation. Negative ratings only. No default: an unset reason means the caller was not asked, `other` means they rejected the list \u2014 collapsing the two would corrupt every breakdown. Same base list as `Reason.code` on /api/v2/memory/update and /delete_by_ids minus `missing`, which a rating cannot express because it always names a memory that exists. (`outdated` \u2014 \"right when written, wrong now\" \u2014 reaches `Reason.code` with the engine's next release; on release-20260901_v15 it is feedback-only.)" + "description": "Structured reason used for analysis; it never triggers mutation. Negative ratings only. No default: an unset reason means the caller was not asked, `other` means they rejected the list \u2014 collapsing the two would corrupt every breakdown. Same base list as `Reason.code` on /api/v2/memory/update and /delete_by_ids minus `missing`, which a rating cannot express because it always names a memory that exists." }, "note": { "anyOf": [ @@ -5429,7 +5615,8 @@ "type": "boolean", "title": "With Readable Episode", "default": false, - "description": "Attach a human-readable rendering to each returned episode, for display only. Ignored for every non-episode `memory_type` rather than rejected." + "deprecated": true, + "description": "Deprecated and has no effect. Still accepted so existing clients that send it keep working; new code should leave it out." }, "filters": { "anyOf": [ @@ -5900,6 +6087,7 @@ "type": "string", "enum": [ "hallucination", + "outdated", "wrong_subject", "wrong_time", "redundant", @@ -5909,7 +6097,7 @@ "other" ], "title": "Code", - "description": "What was wrong, from the fixed list \u2014 hallucination, wrong_subject, wrong_time, redundant, missing, privacy, style, other. Required whenever a reason is given. Same base list as `FeedbackInput.reason` plus `missing`, which only an edit can express (an operator fixes a record that left something out; a rating always names a memory that exists). On the contract this SDK is generated from (engine release-20260901_v15) `outdated` is not yet accepted here \u2014 the engine adds it in its next release; until then record an outdated-type fix as `wrong_time` or `other`." + "description": "What was wrong, from the fixed list \u2014 hallucination, outdated, wrong_subject, wrong_time, redundant, missing, privacy, style, other. Required whenever a reason is given. Same base list as `FeedbackInput.reason` plus `missing`, which only an edit can express (an operator fixes a record that left something out; a rating always names a memory that exists)." }, "note": { "anyOf": [ @@ -6418,18 +6606,6 @@ "title": "Episode", "description": "The episode's stored narrative body. This is the indexed, searchable text." }, - "readable_episode": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Readable Episode", - "description": "Human-readable rendering of `episode`, for display only \u2014 never indexed, filtered or scored. Present as a key but null unless the request asked for it (and the tenant is enabled for it); fall back to `episode` when it is null." - }, "type": { "type": "string", "title": "Type", @@ -6483,7 +6659,26 @@ "score": { "type": "number", "title": "Score", - "description": "Relevance of this episode to the query. What the number means depends on `method`: the hybrid path fuses its two routes into a probability in 0.0\u20131.0 (which is what `min_score` filters on), while keyword and vector pass the underlying engine's own score through \u2014 BM25 has no upper bound and vector similarity depends on the metric. So compare scores within one method, not across methods." + "description": "Relevance of this episode to the query. What the number means depends on `method`: the hybrid path fuses its two routes into a probability in 0.0\u20131.0 (which is what `min_score` filters on), while keyword and vector pass the underlying engine's own score through \u2014 BM25 has no upper bound and vector similarity depends on the metric. On /api/v2/memory/context it is a rank-fusion score instead, roughly 0.01\u20130.05, plus 1.0 for a `cited` episode. So compare scores within one method, not across methods." + }, + "cited": { + "type": "boolean", + "title": "Cited", + "description": "`/context` only: the retrieval model named this memory as one its answer rested on. Always `false` on `/search`.", + "default": false + }, + "readable_episode": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Readable Episode", + "deprecated": true, + "description": "Deprecated and always null. Read `episode` instead." } }, "type": "object", @@ -6793,7 +6988,8 @@ "type": "boolean", "title": "With Readable Episode", "default": false, - "description": "Attach a human-readable rendering of each episode to the returned items, for display only \u2014 it is not indexed, filterable or scored, and callers fall back to `episode` when it is null. Ignored for an agent owner." + "deprecated": true, + "description": "Deprecated and has no effect. Still accepted so existing clients that send it keep working; new code should leave it out." }, "enable_llm_rerank": { "type": "boolean", @@ -8411,18 +8607,6 @@ "title": "Episode", "description": "The episode's stored narrative body. This is the indexed, searchable text." }, - "readable_episode": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Readable Episode", - "description": "Human-readable rendering of `episode`, for display only \u2014 never indexed, filtered or scored. Present as a key but null unless the request asked for it (and the tenant is enabled for it); fall back to `episode` when it is null." - }, "type": { "type": "string", "title": "Type", @@ -8478,6 +8662,19 @@ "title": "Unchanged", "default": false, "description": "True when the patch equalled what was already stored \u2014 nothing was written and nothing downstream was triggered." + }, + "readable_episode": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Readable Episode", + "deprecated": true, + "description": "Deprecated and always null. Read `episode` instead." } }, "type": "object", diff --git a/pyproject.toml b/pyproject.toml index 5c95cce..352c3ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "everos_cloud" -version = "1.2.0" +version = "1.3.0-rc1" description = "EverOS Cloud Memory API" authors = ["EverMind AI "] license = "Apache-2.0" diff --git a/setup.py b/setup.py index 4ff0055..66fa541 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ # prerequisite: setuptools # http://pypi.python.org/pypi/setuptools NAME = "everos-cloud" -VERSION = "1.2.0" +VERSION = "1.3.0-rc1" # Floor = the oldest interpreter CI actually runs (see .github/workflows/ci.yml in # the public repo). Nothing in the generated code needs it this high — the tree # parses as 3.8 — so this is a support policy, not a technical bound, and it is set diff --git a/test/test_context_input.py b/test/test_context_input.py new file mode 100644 index 0000000..008b7d2 --- /dev/null +++ b/test/test_context_input.py @@ -0,0 +1,62 @@ +# coding: utf-8 + +""" + EverOS Cloud Memory API + + Official Python client for the EverOS Cloud Memory API. Add, search, retrieve, and manage long-term memory for your AI applications over a typed interface (pydantic v2, with full type hints). Install and usage guides: https://github.com/EverMind-AI/everos-cloud-sdk-python + + The version of the OpenAPI document: 2.0.0 + Contact: service@evermind.ai + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest + +from everos_cloud.models.context_input import ContextInput + +class TestContextInput(unittest.TestCase): + """ContextInput unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> ContextInput: + """Test ContextInput + include_optional is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `ContextInput` + """ + model = ContextInput() + if include_optional: + return ContextInput( + app_id = 'default', + project_id = 'default', + user_id = '0', + agent_id = '0', + query = '0', + method = 'context', + top_k = 56, + min_score = 0.0, + include_profile = True, + filters = { } + ) + else: + return ContextInput( + query = '0', + ) + """ + + def testContextInput(self): + """Test ContextInput""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_episode_item.py b/test/test_episode_item.py index 8fa9a89..d615530 100644 --- a/test/test_episode_item.py +++ b/test/test_episode_item.py @@ -48,7 +48,6 @@ def make_instance(self, include_optional) -> EpisodeItem: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -59,7 +58,8 @@ def make_instance(self, include_optional) -> EpisodeItem: '' ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), - reflect_state = 'pending' + reflect_state = 'pending', + readable_episode = '' ) else: return EpisodeItem( diff --git a/test/test_get_data.py b/test/test_get_data.py index 60ecd1f..f19e7eb 100644 --- a/test/test_get_data.py +++ b/test/test_get_data.py @@ -50,7 +50,6 @@ def make_instance(self, include_optional) -> GetData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -61,7 +60,8 @@ def make_instance(self, include_optional) -> GetData: '' ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), - reflect_state = 'pending', ) + reflect_state = 'pending', + readable_episode = '', ) ], profiles = [ everos_cloud.models.profile_item.ProfileItem( diff --git a/test/test_memory_api.py b/test/test_memory_api.py index 8d04932..9b84862 100644 --- a/test/test_memory_api.py +++ b/test/test_memory_api.py @@ -83,6 +83,13 @@ def test_replace_tags(self) -> None: """ pass + def test_retrieve_context(self) -> None: + """Test case for retrieve_context + + Context Retrieval [Cloud-only] + """ + pass + def test_search_memory(self) -> None: """Test case for search_memory diff --git a/test/test_search_data.py b/test/test_search_data.py index d4c6a31..cf2fd1b 100644 --- a/test/test_search_data.py +++ b/test/test_search_data.py @@ -50,7 +50,6 @@ def make_instance(self, include_optional) -> SearchData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.search_atomic_fact_item.SearchAtomicFactItem( @@ -63,7 +62,9 @@ def make_instance(self, include_optional) -> SearchData: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - score = 1.337, ) + score = 1.337, + cited = True, + readable_episode = '', ) ], profiles = [ everos_cloud.models.search_profile_item.SearchProfileItem( diff --git a/test/test_search_episode_item.py b/test/test_search_episode_item.py index 8d20103..3b0f819 100644 --- a/test/test_search_episode_item.py +++ b/test/test_search_episode_item.py @@ -48,7 +48,6 @@ def make_instance(self, include_optional) -> SearchEpisodeItem: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.search_atomic_fact_item.SearchAtomicFactItem( @@ -61,7 +60,9 @@ def make_instance(self, include_optional) -> SearchEpisodeItem: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - score = 1.337 + score = 1.337, + cited = True, + readable_episode = '' ) else: return SearchEpisodeItem( diff --git a/test/test_success_envelope_get_data.py b/test/test_success_envelope_get_data.py index e6d498d..c4ee7d1 100644 --- a/test/test_success_envelope_get_data.py +++ b/test/test_success_envelope_get_data.py @@ -52,7 +52,6 @@ def make_instance(self, include_optional) -> SuccessEnvelopeGetData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -63,7 +62,8 @@ def make_instance(self, include_optional) -> SuccessEnvelopeGetData: '' ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), - reflect_state = 'pending', ) + reflect_state = 'pending', + readable_episode = '', ) ], profiles = [ everos_cloud.models.profile_item.ProfileItem( @@ -126,7 +126,6 @@ def make_instance(self, include_optional) -> SuccessEnvelopeGetData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -137,7 +136,8 @@ def make_instance(self, include_optional) -> SuccessEnvelopeGetData: '' ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), - reflect_state = 'pending', ) + reflect_state = 'pending', + readable_episode = '', ) ], profiles = [ everos_cloud.models.profile_item.ProfileItem( diff --git a/test/test_success_envelope_search_data.py b/test/test_success_envelope_search_data.py index 9e28c28..c002737 100644 --- a/test/test_success_envelope_search_data.py +++ b/test/test_success_envelope_search_data.py @@ -52,7 +52,6 @@ def make_instance(self, include_optional) -> SuccessEnvelopeSearchData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.search_atomic_fact_item.SearchAtomicFactItem( @@ -65,7 +64,9 @@ def make_instance(self, include_optional) -> SuccessEnvelopeSearchData: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - score = 1.337, ) + score = 1.337, + cited = True, + readable_episode = '', ) ], profiles = [ everos_cloud.models.search_profile_item.SearchProfileItem( @@ -150,7 +151,6 @@ def make_instance(self, include_optional) -> SuccessEnvelopeSearchData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.search_atomic_fact_item.SearchAtomicFactItem( @@ -163,7 +163,9 @@ def make_instance(self, include_optional) -> SuccessEnvelopeSearchData: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - score = 1.337, ) + score = 1.337, + cited = True, + readable_episode = '', ) ], profiles = [ everos_cloud.models.search_profile_item.SearchProfileItem( diff --git a/test/test_success_envelope_update_data.py b/test/test_success_envelope_update_data.py index 89d15b1..e93ebc3 100644 --- a/test/test_success_envelope_update_data.py +++ b/test/test_success_envelope_update_data.py @@ -50,7 +50,6 @@ def make_instance(self, include_optional) -> SuccessEnvelopeUpdateData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -62,7 +61,8 @@ def make_instance(self, include_optional) -> SuccessEnvelopeUpdateData: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - unchanged = True, ) + unchanged = True, + readable_episode = '', ) ) else: return SuccessEnvelopeUpdateData( @@ -80,7 +80,6 @@ def make_instance(self, include_optional) -> SuccessEnvelopeUpdateData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -92,7 +91,8 @@ def make_instance(self, include_optional) -> SuccessEnvelopeUpdateData: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - unchanged = True, ), + unchanged = True, + readable_episode = '', ), ) """ diff --git a/test/test_update_data.py b/test/test_update_data.py index 57f44cc..07bf548 100644 --- a/test/test_update_data.py +++ b/test/test_update_data.py @@ -48,7 +48,6 @@ def make_instance(self, include_optional) -> UpdateData: summary = '', subject = '', episode = '', - readable_episode = '', type = '', atomic_facts = [ everos_cloud.models.atomic_fact_item.AtomicFactItem( @@ -60,7 +59,8 @@ def make_instance(self, include_optional) -> UpdateData: ], edited_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), reflect_state = 'pending', - unchanged = True + unchanged = True, + readable_episode = '' ) else: return UpdateData(