Add router session mining tools
This commit is contained in:
@@ -35,6 +35,13 @@ from .data_agent_inputs import (
|
||||
load_input_sources,
|
||||
render_source_context,
|
||||
)
|
||||
from .data_agent_router_sessions import (
|
||||
DataAgentRouterSessionError,
|
||||
convert_router_candidates_to_records,
|
||||
profile_router_sessions,
|
||||
sample_router_candidates,
|
||||
search_router_sessions,
|
||||
)
|
||||
from .session_env_vars import get_session_env_vars
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1466,6 +1473,137 @@ def default_tool_registry() -> dict[str, AgentTool]:
|
||||
},
|
||||
handler=_data_agent_render_source_context_tool,
|
||||
),
|
||||
AgentTool(
|
||||
name='data_agent_profile_router_sessions',
|
||||
description=(
|
||||
'Profile local router_session_parquet date partitions before mining online sessions. '
|
||||
'Returns schema, sampled row count, distributions, and example sessions.'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'dates': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': 'Date partitions such as ["20260428"]. Resolved under router_session_parquet/date=YYYYMMDD.',
|
||||
},
|
||||
'paths': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': 'Workspace-relative parquet files or date directories. Use when dates is not enough.',
|
||||
},
|
||||
'max_files': {'type': 'integer', 'minimum': 1, 'maximum': 200},
|
||||
'max_rows_per_file': {'type': 'integer', 'minimum': 1, 'maximum': 100000},
|
||||
'examples_limit': {'type': 'integer', 'minimum': 0, 'maximum': 20},
|
||||
},
|
||||
},
|
||||
handler=_data_agent_profile_router_sessions_tool,
|
||||
),
|
||||
AgentTool(
|
||||
name='data_agent_search_router_sessions',
|
||||
description=(
|
||||
'Search local router_session_parquet data with reviewable filters such as device, domain, intent, func, '
|
||||
'query keywords, regex, turn count, and match scope. Returns turn-level candidates with prev turns.'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'dates': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': 'Date partitions such as ["20260428"]. Resolved under router_session_parquet/date=YYYYMMDD.',
|
||||
},
|
||||
'paths': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': 'Workspace-relative parquet files or date directories. Use when dates is not enough.',
|
||||
},
|
||||
'devices': {'type': 'array', 'items': {'type': 'string'}},
|
||||
'domains': {'type': 'array', 'items': {'type': 'string'}},
|
||||
'intents': {'type': 'array', 'items': {'type': 'string'}},
|
||||
'funcs': {'type': 'array', 'items': {'type': 'string'}},
|
||||
'query_keywords': {'type': 'array', 'items': {'type': 'string'}},
|
||||
'keyword_match_mode': {'type': 'string', 'enum': ['any', 'all']},
|
||||
'query_regex': {'type': 'string'},
|
||||
'match_scope': {'type': 'string', 'enum': ['any_turn', 'last_turn']},
|
||||
'turn_count_min': {'type': 'integer', 'minimum': 1},
|
||||
'turn_count_max': {'type': 'integer', 'minimum': 1},
|
||||
'max_files': {'type': 'integer', 'minimum': 1, 'maximum': 200},
|
||||
'max_rows_per_file': {'type': 'integer', 'minimum': 1, 'maximum': 100000},
|
||||
'max_candidates': {'type': 'integer', 'minimum': 1, 'maximum': 5000},
|
||||
},
|
||||
},
|
||||
handler=_data_agent_search_router_sessions_tool,
|
||||
),
|
||||
AgentTool(
|
||||
name='data_agent_sample_router_candidates',
|
||||
description=(
|
||||
'Sample router session candidates for human review and return candidate-level summary statistics. '
|
||||
'Use after data_agent_search_router_sessions.'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'candidates': {
|
||||
'oneOf': [
|
||||
{'type': 'array'},
|
||||
{'type': 'object'},
|
||||
{'type': 'string'},
|
||||
],
|
||||
'description': 'Candidates array, search result object, or JSON string containing candidates.',
|
||||
},
|
||||
'sample_size': {'type': 'integer', 'minimum': 1, 'maximum': 1000},
|
||||
'strategy': {'type': 'string', 'enum': ['random', 'first', 'stride']},
|
||||
'seed': {'type': 'integer'},
|
||||
},
|
||||
'required': ['candidates'],
|
||||
},
|
||||
handler=_data_agent_sample_router_candidates_tool,
|
||||
),
|
||||
AgentTool(
|
||||
name='data_agent_convert_router_candidates_to_records',
|
||||
description=(
|
||||
'Convert reviewed online router session candidates directly into canonical data-agent records. '
|
||||
'Use this when the user wants to keep mined online data as samples; do not use generation-plan tools for this path.'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'candidates': {
|
||||
'oneOf': [
|
||||
{'type': 'array'},
|
||||
{'type': 'object'},
|
||||
{'type': 'string'},
|
||||
],
|
||||
'description': 'Candidates array, search/sample result object, or JSON string containing candidates.',
|
||||
},
|
||||
'dataset_label': {'type': 'string'},
|
||||
'default_target': {
|
||||
'type': 'string',
|
||||
'description': 'Target label used for included candidates unless review_decisions provides a target.',
|
||||
},
|
||||
'review_decisions': {
|
||||
'type': 'array',
|
||||
'description': 'Optional include/exclude/uncertain decisions keyed by semantic_session_id or req_id.',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'semantic_session_id': {'type': 'string'},
|
||||
'req_id': {'type': 'string'},
|
||||
'matched_turn_index': {'type': 'integer'},
|
||||
'decision': {'type': 'string', 'enum': ['include', 'exclude', 'uncertain']},
|
||||
'target': {'type': 'string'},
|
||||
'notes': {'type': 'string'},
|
||||
},
|
||||
},
|
||||
},
|
||||
'batch_id': {'type': 'string'},
|
||||
'include_uncertain': {'type': 'boolean'},
|
||||
},
|
||||
'required': ['candidates', 'dataset_label'],
|
||||
},
|
||||
handler=_data_agent_convert_router_candidates_to_records_tool,
|
||||
),
|
||||
AgentTool(
|
||||
name='data_agent_show_generation_plan',
|
||||
description='Show a data-agent generation plan for human review.',
|
||||
@@ -1689,6 +1827,15 @@ def _coerce_int(arguments: dict[str, Any], key: str, default: int) -> int:
|
||||
return value
|
||||
|
||||
|
||||
def _optional_int(arguments: dict[str, Any], key: str) -> int | None:
|
||||
value = arguments.get(key)
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, bool) or not isinstance(value, int):
|
||||
raise ToolExecutionError(f'{key} must be an integer')
|
||||
return value
|
||||
|
||||
|
||||
def _coerce_float(arguments: dict[str, Any], key: str, default: float) -> float:
|
||||
value = arguments.get(key, default)
|
||||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||||
@@ -1855,6 +2002,88 @@ def _data_agent_render_source_context_tool(arguments: dict[str, Any], context: T
|
||||
return data_agent_dumps_payload(payload)
|
||||
|
||||
|
||||
def _data_agent_profile_router_sessions_tool(arguments: dict[str, Any], context: ToolExecutionContext) -> str:
|
||||
try:
|
||||
payload = profile_router_sessions(
|
||||
context.root,
|
||||
dates=_optional_string_list(arguments, 'dates'),
|
||||
paths=_optional_string_list(arguments, 'paths'),
|
||||
max_files=_coerce_int(arguments, 'max_files', 8),
|
||||
max_rows_per_file=_coerce_int(arguments, 'max_rows_per_file', 2000),
|
||||
examples_limit=_coerce_int(arguments, 'examples_limit', 3),
|
||||
)
|
||||
except DataAgentRouterSessionError as exc:
|
||||
raise ToolExecutionError(str(exc)) from exc
|
||||
return data_agent_dumps_payload(payload)
|
||||
|
||||
|
||||
def _data_agent_search_router_sessions_tool(arguments: dict[str, Any], context: ToolExecutionContext) -> str:
|
||||
try:
|
||||
payload = search_router_sessions(
|
||||
context.root,
|
||||
dates=_optional_string_list(arguments, 'dates'),
|
||||
paths=_optional_string_list(arguments, 'paths'),
|
||||
devices=_optional_string_list(arguments, 'devices'),
|
||||
domains=_optional_string_list(arguments, 'domains'),
|
||||
intents=_optional_string_list(arguments, 'intents'),
|
||||
funcs=_optional_string_list(arguments, 'funcs'),
|
||||
query_keywords=_optional_string_list(arguments, 'query_keywords'),
|
||||
keyword_match_mode=_optional_string(arguments, 'keyword_match_mode') or 'any',
|
||||
query_regex=_optional_string(arguments, 'query_regex'),
|
||||
match_scope=_optional_string(arguments, 'match_scope') or 'any_turn',
|
||||
turn_count_min=_optional_int(arguments, 'turn_count_min'),
|
||||
turn_count_max=_optional_int(arguments, 'turn_count_max'),
|
||||
max_files=_coerce_int(arguments, 'max_files', 20),
|
||||
max_rows_per_file=_coerce_int(arguments, 'max_rows_per_file', 5000),
|
||||
max_candidates=_coerce_int(arguments, 'max_candidates', 200),
|
||||
)
|
||||
except DataAgentRouterSessionError as exc:
|
||||
raise ToolExecutionError(str(exc)) from exc
|
||||
return data_agent_dumps_payload(payload)
|
||||
|
||||
|
||||
def _data_agent_sample_router_candidates_tool(arguments: dict[str, Any], _context: ToolExecutionContext) -> str:
|
||||
if 'candidates' not in arguments:
|
||||
raise ToolExecutionError('candidates is required')
|
||||
try:
|
||||
payload = sample_router_candidates(
|
||||
arguments['candidates'],
|
||||
sample_size=_coerce_int(arguments, 'sample_size', 100),
|
||||
strategy=_optional_string(arguments, 'strategy') or 'random',
|
||||
seed=_coerce_int(arguments, 'seed', 20260430),
|
||||
)
|
||||
except DataAgentRouterSessionError as exc:
|
||||
raise ToolExecutionError(str(exc)) from exc
|
||||
return data_agent_dumps_payload(payload)
|
||||
|
||||
|
||||
def _data_agent_convert_router_candidates_to_records_tool(
|
||||
arguments: dict[str, Any],
|
||||
_context: ToolExecutionContext,
|
||||
) -> str:
|
||||
if 'candidates' not in arguments:
|
||||
raise ToolExecutionError('candidates is required')
|
||||
review_decisions = arguments.get('review_decisions')
|
||||
if review_decisions is not None and not isinstance(review_decisions, list):
|
||||
raise ToolExecutionError('review_decisions must be an array')
|
||||
include_uncertain = arguments.get('include_uncertain', False)
|
||||
if not isinstance(include_uncertain, bool):
|
||||
raise ToolExecutionError('include_uncertain must be a boolean')
|
||||
try:
|
||||
payload = convert_router_candidates_to_records(
|
||||
arguments['candidates'],
|
||||
dataset_label=_require_string(arguments, 'dataset_label'),
|
||||
default_target=_optional_string(arguments, 'default_target'),
|
||||
review_decisions=review_decisions,
|
||||
batch_id=_optional_string(arguments, 'batch_id') or 'router',
|
||||
include_uncertain=include_uncertain,
|
||||
)
|
||||
payload['validation'] = validate_dataset_records(payload['records'])
|
||||
except (DataAgentRouterSessionError, DataRecordError) as exc:
|
||||
raise ToolExecutionError(str(exc)) from exc
|
||||
return data_agent_dumps_payload(payload)
|
||||
|
||||
|
||||
def _data_agent_show_generation_plan_tool(arguments: dict[str, Any], context: ToolExecutionContext) -> str:
|
||||
try:
|
||||
payload = get_generation_plan(
|
||||
|
||||
Reference in New Issue
Block a user