orchestrator-core 4.6.2__py3-none-any.whl → 4.6.3rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. orchestrator/__init__.py +1 -1
  2. orchestrator/api/api_v1/endpoints/search.py +44 -34
  3. orchestrator/{search/retrieval/utils.py → cli/search/display.py} +4 -29
  4. orchestrator/cli/search/search_explore.py +22 -24
  5. orchestrator/cli/search/speedtest.py +11 -9
  6. orchestrator/db/models.py +6 -6
  7. orchestrator/log_config.py +2 -0
  8. orchestrator/schemas/search.py +1 -1
  9. orchestrator/schemas/search_requests.py +59 -0
  10. orchestrator/search/agent/handlers.py +129 -0
  11. orchestrator/search/agent/prompts.py +54 -33
  12. orchestrator/search/agent/state.py +9 -24
  13. orchestrator/search/agent/tools.py +223 -144
  14. orchestrator/search/agent/validation.py +80 -0
  15. orchestrator/search/{schemas → aggregations}/__init__.py +20 -0
  16. orchestrator/search/aggregations/base.py +201 -0
  17. orchestrator/search/core/types.py +3 -2
  18. orchestrator/search/filters/__init__.py +4 -0
  19. orchestrator/search/filters/definitions.py +22 -1
  20. orchestrator/search/filters/numeric_filter.py +3 -3
  21. orchestrator/search/llm_migration.py +2 -1
  22. orchestrator/search/query/__init__.py +90 -0
  23. orchestrator/search/query/builder.py +285 -0
  24. orchestrator/search/query/engine.py +162 -0
  25. orchestrator/search/{retrieval → query}/exceptions.py +38 -7
  26. orchestrator/search/query/mixins.py +95 -0
  27. orchestrator/search/query/queries.py +129 -0
  28. orchestrator/search/query/results.py +252 -0
  29. orchestrator/search/{retrieval/query_state.py → query/state.py} +31 -11
  30. orchestrator/search/{retrieval → query}/validation.py +58 -1
  31. orchestrator/search/retrieval/__init__.py +0 -5
  32. orchestrator/search/retrieval/pagination.py +7 -8
  33. orchestrator/search/retrieval/retrievers/base.py +9 -9
  34. {orchestrator_core-4.6.2.dist-info → orchestrator_core-4.6.3rc1.dist-info}/METADATA +6 -6
  35. {orchestrator_core-4.6.2.dist-info → orchestrator_core-4.6.3rc1.dist-info}/RECORD +38 -32
  36. orchestrator/search/retrieval/builder.py +0 -127
  37. orchestrator/search/retrieval/engine.py +0 -197
  38. orchestrator/search/schemas/parameters.py +0 -133
  39. orchestrator/search/schemas/results.py +0 -80
  40. /orchestrator/search/{export.py → query/export.py} +0 -0
  41. {orchestrator_core-4.6.2.dist-info → orchestrator_core-4.6.3rc1.dist-info}/WHEEL +0 -0
  42. {orchestrator_core-4.6.2.dist-info → orchestrator_core-4.6.3rc1.dist-info}/licenses/LICENSE +0 -0
@@ -19,6 +19,7 @@ from pydantic_ai import RunContext
19
19
  from pydantic_ai.ag_ui import StateDeps
20
20
 
21
21
  from orchestrator.search.agent.state import SearchState
22
+ from orchestrator.search.core.types import ActionType
22
23
 
23
24
  logger = structlog.get_logger(__name__)
24
25
 
@@ -33,7 +34,6 @@ async def get_base_instructions() -> str:
33
34
 
34
35
  Your ultimate goal is to **find information** that answers the user's request.
35
36
 
36
- To do this, you will perform either a broad search or a filtered search.
37
37
  For **filtered searches**, your primary method is to **construct a valid `FilterTree` object**.
38
38
  To do this correctly, you must infer the exact structure, operators, and nesting rules from the Pydantic schema of the `set_filter_tree` tool itself.
39
39
 
@@ -48,15 +48,19 @@ async def get_base_instructions() -> str:
48
48
  ---
49
49
  ### 3. Execution Workflow
50
50
 
51
- Follow these steps in strict order:
51
+ Follow these steps:
52
52
 
53
- 1. **Set Context**: If the user is asking for a NEW search, call `start_new_search`.
54
- 2. **Analyze for Filters**: Based on the user's request, decide if specific filters are necessary.
55
- - **If filters ARE required**, follow these sub-steps:
56
- a. **Gather Intel**: Identify all needed field names, then call `discover_filter_paths` and `get_valid_operators` **once each** to get all required information.
57
- b. **Construct FilterTree**: Build the `FilterTree` object.
58
- c. **Set Filters**: Call `set_filter_tree`.
59
- 3. **Execute**: Call `run_search`. This is done for both filtered and non-filtered searches.
53
+ 1. **Set Context**: Call `start_new_search` with appropriate entity_type and action
54
+ 2. **Set Filters** (if needed): Discover paths, build FilterTree, call `set_filter_tree`
55
+ - IMPORTANT: Temporal constraints like "in 2025", "in January", "between X and Y" require filters on datetime fields
56
+ - Filters restrict WHICH records to include; grouping controls HOW to aggregate them
57
+ 3. **Set Grouping/Aggregations** (for COUNT/AGGREGATE):
58
+ - For temporal grouping (per month, per year, per day, etc.): Use `set_temporal_grouping`
59
+ - For regular grouping (by status, by name, etc.): Use `set_grouping`
60
+ - For aggregations: Use `set_aggregations`
61
+ 4. **Execute**:
62
+ - For SELECT action: Call `run_search()`
63
+ - For COUNT/AGGREGATE actions: Call `run_aggregation()`
60
64
 
61
65
  After search execution, follow the dynamic instructions based on the current state.
62
66
 
@@ -73,31 +77,46 @@ async def get_base_instructions() -> str:
73
77
  async def get_dynamic_instructions(ctx: RunContext[StateDeps[SearchState]]) -> str:
74
78
  """Dynamically provides 'next step' coaching based on the current state."""
75
79
  state = ctx.deps.state
76
- param_state_str = json.dumps(state.parameters, indent=2, default=str) if state.parameters else "Not set."
77
- results_count = state.results_data.total_count if state.results_data else 0
80
+ query_state_str = json.dumps(state.query.model_dump(), indent=2, default=str) if state.query else "Not set."
81
+ results_count = state.results_count or 0
82
+ action = state.action or ActionType.SELECT
78
83
 
79
- if state.export_data:
84
+ if not state.query:
80
85
  next_step_guidance = (
81
- "INSTRUCTION: Export has been prepared successfully. "
82
- "Simply confirm to the user that the export is ready for download. "
83
- "DO NOT include or mention the download URL - the UI will display it automatically."
84
- )
85
- elif not state.parameters or not state.parameters.get("entity_type"):
86
- next_step_guidance = (
87
- "INSTRUCTION: The search context is not set. Your next action is to call `start_new_search`."
86
+ f"INSTRUCTION: The search context is not set. Your next action is to call `start_new_search`. "
87
+ f"For counting or aggregation queries, set action='{ActionType.COUNT.value}' or action='{ActionType.AGGREGATE.value}'."
88
88
  )
89
89
  elif results_count > 0:
90
- next_step_guidance = dedent(
91
- f"""
92
- INSTRUCTION: Search completed successfully.
93
- Found {results_count} results containing only: entity_id, title, score.
94
-
95
- Choose your next action based on what the user requested:
96
- 1. **Broad/generic search** (e.g., 'show me subscriptions'): Confirm search completed and report count. Do nothing else.
97
- 2. **Question answerable with entity_id/title/score**: Answer directly using the current results.
98
- 3. **Question requiring other details**: Call `fetch_entity_details` first, then answer with the detailed data.
99
- 4. **Export request** (phrases like 'export', 'download', 'save as CSV'): Call `prepare_export` directly.
100
- """
90
+ if action in (ActionType.COUNT, ActionType.AGGREGATE):
91
+ # Aggregation completed
92
+ next_step_guidance = (
93
+ "INSTRUCTION: Aggregation completed successfully. "
94
+ "The results are already displayed in the UI. "
95
+ "Simply confirm completion to the user in a brief sentence. "
96
+ "DO NOT repeat, summarize, or restate the aggregation data."
97
+ )
98
+ else:
99
+ # Search completed
100
+ next_step_guidance = dedent(
101
+ f"""
102
+ INSTRUCTION: Search completed successfully.
103
+ Found {results_count} results containing only: entity_id, title, score.
104
+
105
+ Choose your next action based on what the user requested:
106
+ 1. **Broad/generic search** (e.g., 'show me subscriptions'): Confirm search completed and report count. Do not repeat the results.
107
+ 2. **Question answerable with entity_id/title/score**: Answer directly using the current results.
108
+ 3. **Question requiring other details**: Call `fetch_entity_details` first, then answer with the detailed data.
109
+ 4. **Export request** (phrases like 'export', 'download', 'save as CSV'): Call `prepare_export` directly. Simply confirm the export is ready. Do not repeat the results.
110
+ """
111
+ )
112
+ elif action in (ActionType.COUNT, ActionType.AGGREGATE):
113
+ # COUNT or AGGREGATE action but no results yet
114
+ next_step_guidance = (
115
+ "INSTRUCTION: Aggregation context is set. "
116
+ "For temporal queries (per month, per year, over time): call `set_temporal_grouping` with datetime field and period. "
117
+ "For regular grouping: call `set_grouping` with paths to group by. "
118
+ f"For {ActionType.AGGREGATE.value.upper()}: call `set_aggregations` with aggregation specs. "
119
+ "Then call `run_aggregation`."
101
120
  )
102
121
  else:
103
122
  next_step_guidance = (
@@ -106,17 +125,19 @@ async def get_dynamic_instructions(ctx: RunContext[StateDeps[SearchState]]) -> s
106
125
  "If no specific filters are needed, you can proceed directly to `run_search`."
107
126
  )
108
127
 
128
+ status_summary = f"Results: {results_count}" if results_count > 0 else "No results yet"
129
+
109
130
  return dedent(
110
131
  f"""
111
132
  ---
112
133
  ## CURRENT STATE
113
134
 
114
- **Current Search Parameters:**
135
+ **Current Query:**
115
136
  ```json
116
- {param_state_str}
137
+ {query_state_str}
117
138
  ```
118
139
 
119
- **Current Results Count:** {results_count}
140
+ **Status:** {status_summary}
120
141
 
121
142
  ---
122
143
  ## NEXT ACTION REQUIRED
@@ -11,37 +11,22 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
 
14
- from typing import Any
15
14
  from uuid import UUID
16
15
 
17
16
  from pydantic import BaseModel
18
17
 
19
- from orchestrator.search.schemas.results import SearchResult
18
+ from orchestrator.search.core.types import ActionType
19
+ from orchestrator.search.query.queries import Query
20
20
 
21
21
 
22
- class ExportData(BaseModel):
23
- """Export metadata for download."""
24
-
25
- action: str = "export"
26
- query_id: str
27
- download_url: str
28
- message: str
29
-
30
-
31
- class SearchResultsData(BaseModel):
32
- """Search results data for frontend display and agent context."""
33
-
34
- action: str = "view_results"
35
- query_id: str
36
- results_url: str
37
- total_count: int
38
- message: str
39
- results: list[SearchResult] = []
22
+ class SearchState(BaseModel):
23
+ """Agent state for search operations.
40
24
 
25
+ Tracks the current search context and execution status.
26
+ """
41
27
 
42
- class SearchState(BaseModel):
43
28
  run_id: UUID | None = None
44
29
  query_id: UUID | None = None
45
- parameters: dict[str, Any] | None = None
46
- results_data: SearchResultsData | None = None
47
- export_data: ExportData | None = None
30
+ action: ActionType | None = None
31
+ query: Query | None = None
32
+ results_count: int | None = None # Number of results from last executed search/aggregation