from __future__ import annotations from collections.abc import Mapping from ..agent_tool_core import AgentTool, ToolHandler from ._builder import resolve_handler def build_data_agent_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentTool]: """构建数据 Agent 专用工具声明。 这里仅保存工具名称、描述和 JSON Schema,具体执行逻辑由调用方传入的 handler 提供。 这样新增数据 Agent 工具时,可以优先在本文件补充声明,再在实现模块中补充 handler。 """ return [ AgentTool( name='data_agent_prepare_generation_goal', description=( 'Create a pending data-agent generation goal from source evidence or user rules. ' 'Use this before data_agent_prepare_generation_plan; show the returned goal to the user and wait for confirmation.' ), parameters={ 'type': 'object', 'properties': { 'dataset_label': {'type': 'string'}, 'goal_summary': {'type': 'string'}, 'target': { 'type': 'string', 'description': 'Single full target expression, e.g. Agent(tag="餐饮服务") or a function call. For multi-target boundary tasks, use target_definitions.', }, 'target_definitions': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'target': { 'type': 'string', 'description': 'Full target expression, e.g. Agent(tag="餐饮服务"); do not shorten to a display name.', }, 'rule': {'type': 'string'}, }, 'required': ['target'], }, }, 'plan_hint': { 'type': 'string', 'description': ( 'Optional plain-language hint for the later plan, such as ' '"建议先生成 50 条单轮,输出到 tasks/.../records.jsonl". ' 'Structured total_count, turn_mix, and output_path belong to data_agent_prepare_generation_plan.' ), }, 'coverage': {'type': 'string'}, 'exclusions': {'type': 'string'}, 'open_questions': { 'type': 'array', 'items': {'type': 'string'}, }, 'source_refs': { 'type': 'array', 'items': {'type': 'string'}, }, 'notes': {'type': 'string'}, }, 'required': ['dataset_label', 'goal_summary', 'coverage', 'exclusions'], }, handler=resolve_handler(handlers, 'data_agent_prepare_generation_goal', 'data-agent'), ), AgentTool( name='data_agent_confirm_generation_goal', description=( 'Mark a pending data-agent generation goal as confirmed after the user explicitly approves it. ' 'The returned confirmed_goal_id is required by data_agent_prepare_generation_plan.' ), parameters={ 'type': 'object', 'properties': { 'goal_id': {'type': 'string'}, 'confirmation': { 'type': 'string', 'description': 'The user approval text, such as 确认, 开始生成, or approve.', }, 'reviewed_revision': { 'type': 'integer', 'description': 'The goal revision shown to the user before confirmation.', }, }, 'required': ['goal_id', 'confirmation'], }, handler=resolve_handler(handlers, 'data_agent_confirm_generation_goal', 'data-agent'), ), AgentTool( name='data_agent_prepare_generation_plan', description=( 'Create a pending data-agent generation plan. Use this before generating dataset draft text; ' 'requires confirmed_goal_id from data_agent_confirm_generation_goal; show the returned plan to the user and wait for confirmation.' ), parameters={ 'type': 'object', 'properties': { 'confirmed_goal_id': { 'type': 'string', 'description': 'Returned by data_agent_confirm_generation_goal after the user reviews a separate generation goal. Omit only when direct_review is true.', }, 'direct_review': { 'type': 'boolean', 'description': ( 'Set true for simple hand-written rules where the user already supplied target labels ' 'and wants generation; this creates one combined goal+plan review instead of a separate goal review.' ), }, 'dataset_label': {'type': 'string'}, 'target': { 'type': 'string', 'description': 'Single full target expression. For multi-target boundary tasks, omit this and fill target_definitions.', }, 'target_definitions': { 'type': 'array', 'description': 'Optional target labels for multi-class boundary data.', 'items': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'target': { 'type': 'string', 'description': 'Full target expression inherited from the confirmed goal, e.g. Agent(tag="餐饮服务").', }, 'rule': {'type': 'string'}, }, 'required': ['target'], }, }, 'total_count': {'type': 'integer', 'minimum': 1}, 'turn_mix': {'type': 'string', 'description': 'Single-turn and multi-turn count or ratio.'}, 'coverage': {'type': 'string', 'description': 'Query types, intent boundaries, or error types to cover.'}, 'exclusions': {'type': 'string', 'description': 'Negative examples or boundaries to avoid.'}, 'output_path': { 'type': 'string', 'description': ( 'Requested records path. The runtime normalizes canonical records to the current ' 'session output/records.jsonl; use output/records.jsonl and do not add dataset ' 'subdirectories or random names.' ), }, 'notes': {'type': 'string'}, }, 'required': [ 'dataset_label', 'total_count', 'turn_mix', 'coverage', 'exclusions', 'output_path', ], }, handler=resolve_handler(handlers, 'data_agent_prepare_generation_plan', 'data-agent'), ), AgentTool( name='data_agent_load_input_sources', description=( 'Load data-agent input files or directories and extract structured paragraphs and table previews ' 'from xlsx, csv, docx, pdf, txt, md, json, or jsonl sources.' ), parameters={ 'type': 'object', 'properties': { 'paths': { 'type': 'array', 'items': {'type': 'string'}, 'description': 'Workspace-relative files or directories to load.', }, 'max_files': {'type': 'integer', 'minimum': 1, 'maximum': 100}, 'max_paragraphs_per_file': {'type': 'integer', 'minimum': 1, 'maximum': 300}, 'max_tables_per_file': {'type': 'integer', 'minimum': 1, 'maximum': 100}, 'max_rows_per_table': {'type': 'integer', 'minimum': 1, 'maximum': 200}, 'max_cell_chars': {'type': 'integer', 'minimum': 20, 'maximum': 2000}, }, 'required': ['paths'], }, handler=resolve_handler(handlers, 'data_agent_load_input_sources', 'data-agent'), ), AgentTool( name='data_agent_extract_case_evidence', description=( 'Extract query/badcase evidence from loaded input sources or source paths. ' 'Profiles table columns first and returns required questions when query or expected label columns are ambiguous.' ), parameters={ 'type': 'object', 'properties': { 'paths': { 'type': 'array', 'items': {'type': 'string'}, 'description': 'Workspace-relative files or directories. Omit when loaded_sources is provided.', }, 'loaded_sources': { 'type': 'object', 'description': 'Output from data_agent_load_input_sources.', }, 'field_mapping': { 'type': 'object', 'description': 'Optional role-to-column mapping, e.g. {"query":"query","expected_label":"预期domain"}.', }, 'max_cases': {'type': 'integer', 'minimum': 1, 'maximum': 1000}, }, }, handler=resolve_handler(handlers, 'data_agent_extract_case_evidence', 'data-agent'), ), AgentTool( name='data_agent_render_source_context', description=( 'Render loaded data-agent sources into LLM-readable evidence text with source refs. ' 'Use this after data_agent_load_input_sources before asking the model to structure product semantics.' ), parameters={ 'type': 'object', 'properties': { 'paths': { 'type': 'array', 'items': {'type': 'string'}, 'description': 'Workspace-relative files or directories. Omit when loaded_sources is provided.', }, 'loaded_sources': { 'type': 'object', 'description': 'Output from data_agent_load_input_sources.', }, 'max_chars': {'type': 'integer', 'minimum': 1000, 'maximum': 200000}, 'max_tables_per_source': {'type': 'integer', 'minimum': 1, 'maximum': 100}, 'max_rows_per_table': {'type': 'integer', 'minimum': 1, 'maximum': 200}, 'focus_keywords': { 'type': 'array', 'items': {'type': 'string'}, 'description': 'Optional keywords used to keep only matching paragraphs/table rows.', }, }, }, handler=resolve_handler(handlers, 'data_agent_render_source_context', 'data-agent'), ), 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': 'Parquet files or date directories. Relative paths resolve from workspace root; absolute paths are allowed for external online data.', }, '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=resolve_handler(handlers, 'data_agent_profile_router_sessions', 'data-agent'), ), 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': 'Parquet files or date directories. Relative paths resolve from workspace root; absolute paths are allowed for external online data.', }, '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=resolve_handler(handlers, 'data_agent_search_router_sessions', 'data-agent'), ), 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=resolve_handler(handlers, 'data_agent_sample_router_candidates', 'data-agent'), ), 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.', }, 'default_complex': { 'type': 'boolean', 'description': 'Default complex dimension for included candidates unless review_decisions provides complex.', }, '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'}, 'complex': {'type': 'boolean'}, 'notes': {'type': 'string'}, }, }, }, 'batch_id': {'type': 'string'}, 'include_uncertain': {'type': 'boolean'}, }, 'required': ['candidates', 'dataset_label'], }, handler=resolve_handler(handlers, 'data_agent_convert_router_candidates_to_records', 'data-agent'), ), AgentTool( name='data_agent_show_generation_plan', description='Show a data-agent generation plan for human review.', parameters={ 'type': 'object', 'properties': { 'plan_id': {'type': 'string'}, }, 'required': ['plan_id'], }, handler=resolve_handler(handlers, 'data_agent_show_generation_plan', 'data-agent'), ), AgentTool( name='data_agent_update_generation_plan', description=( 'Update a pending data-agent generation plan from human review feedback, then show it again for review.' ), parameters={ 'type': 'object', 'properties': { 'plan_id': {'type': 'string'}, 'review_feedback': { 'type': 'string', 'description': 'Human review feedback that explains why the plan is changing.', }, 'updates': { 'type': 'object', 'description': 'Fields to update on the plan.', }, }, 'required': ['plan_id', 'review_feedback', 'updates'], }, handler=resolve_handler(handlers, 'data_agent_update_generation_plan', 'data-agent'), ), AgentTool( name='data_agent_confirm_generation_plan', description=( 'Mark a pending data-agent generation plan as confirmed after the user explicitly approves it.' ), parameters={ 'type': 'object', 'properties': { 'plan_id': {'type': 'string'}, 'confirmation': { 'type': 'string', 'description': 'The user approval text, such as 确认, 开始生成, or approve.', }, 'reviewed_revision': { 'type': 'integer', 'description': 'The plan revision shown to the user before confirmation.', }, }, 'required': ['plan_id', 'confirmation'], }, handler=resolve_handler(handlers, 'data_agent_confirm_generation_plan', 'data-agent'), ), AgentTool( name='data_agent_normalize_dataset_draft', description=( 'Parse dataset draft text written with 用户/小爱/target blocks and build canonical ' 'data-agent records with generated record IDs, timestamps, source metadata, context, labels, ' 'and dimensions.complex. ' 'Generated data requires a confirmed_plan_id from data_agent_confirm_generation_plan. ' 'Use draft_path instead of draft_text when the draft is large or contains many quoted targets.' ), parameters={ 'type': 'object', 'properties': { 'draft_text': { 'type': 'string', 'description': 'Dataset draft text in dataset draft text v1 format. Each case should include complex: true/false. Provide exactly one of draft_text or draft_path.', }, 'draft_path': { 'type': 'string', 'description': 'Path to a UTF-8 dataset draft text file. Prefer this for large drafts. Provide exactly one of draft_text or draft_path.', }, 'batch_id': { 'type': 'string', 'description': 'Batch identifier used in generated record_id values.', }, 'source_type': { 'type': 'string', 'enum': ['generated', 'online', 'manual', 'mixed'], }, 'base_timestamp': { 'type': 'integer', 'description': 'Optional millisecond timestamp for deterministic generated records.', }, 'timestamp_step_ms': { 'type': 'integer', 'minimum': 1, 'description': 'Millisecond gap between adjacent turns. Defaults to 60000.', }, 'default_request_id': { 'type': 'string', 'description': 'Request ID to use when a case does not provide a real request_id.', }, 'confirmed_plan_id': { 'type': 'string', 'description': 'Required for generated data; returned by data_agent_confirm_generation_plan.', }, }, }, handler=resolve_handler(handlers, 'data_agent_normalize_dataset_draft', 'data-agent'), ), AgentTool( name='data_agent_validate_dataset_records', description=( 'Validate canonical data-agent records for required fields, labels, source metadata, ' 'prev_session structure, dimensions.complex, timestamp order, and 5-minute prompt stitching constraints.' ), parameters={ 'type': 'object', 'properties': { 'records': { 'oneOf': [ {'type': 'array'}, {'type': 'string'}, ], 'description': 'Canonical records as an array, or a JSON string containing the array. Provide exactly one of records or records_path.', }, 'records_path': { 'type': 'string', 'description': 'Path to records JSON/JSONL. Prefer this when records are large. Provide exactly one of records or records_path.', }, }, }, handler=resolve_handler(handlers, 'data_agent_validate_dataset_records', 'data-agent'), ), AgentTool( name='data_agent_export_dataset_records', description=( 'Validate canonical data-agent records and write them to a workspace file. ' 'Defaults to compact JSONL, one canonical record per line, and also writes records.csv ' 'in the same output directory for human sharing. The table function column combines ' 'complex=true/false and label.target.' ), parameters={ 'type': 'object', 'properties': { 'records': { 'oneOf': [ {'type': 'array'}, {'type': 'string'}, ], 'description': 'Canonical records as an array, or a JSON string containing the array. Provide exactly one of records or records_path.', }, 'records_path': { 'type': 'string', 'description': 'Path to records JSON/JSONL. Prefer this when records are large. Provide exactly one of records or records_path.', }, 'output_path': { 'type': 'string', 'description': ( 'Requested workspace-relative path. For canonical records, the runtime normalizes this ' 'to the current session output/records.jsonl or output/records.json; do not encode ' 'dataset names or timestamps in the filename.' ), }, 'output_format': { 'type': 'string', 'enum': ['jsonl', 'json'], 'description': 'Defaults to jsonl. json writes a compact JSON array.', }, 'require_validation_ok': { 'type': 'boolean', 'description': 'When true, refuse to write records with validation errors.', }, 'overwrite': { 'type': 'boolean', 'description': 'When false, refuse to overwrite an existing file.', }, 'export_table': { 'type': 'boolean', 'description': 'Defaults to true. Also write records.csv with the shared spreadsheet columns.', }, }, 'required': ['output_path'], }, handler=resolve_handler(handlers, 'data_agent_export_dataset_records', 'data-agent'), ), AgentTool( name='data_agent_export_training_jsonl', description=( 'Convert canonical data-agent records into training JSONL. Each line has system, instruction, ' 'and output. Output combines dimensions.complex and label.target as two lines. ' 'The instruction contains 知识注入, 系统状态, 对话历史, 当前query, and function sections.' ), parameters={ 'type': 'object', 'properties': { 'records': { 'oneOf': [ {'type': 'array'}, {'type': 'string'}, ], 'description': 'Canonical records as an array, or a JSON string containing the array. Provide exactly one of records or records_path.', }, 'records_path': { 'type': 'string', 'description': 'Path to records JSON/JSONL. Prefer this when records are large. Provide exactly one of records or records_path.', }, 'output_path': { 'type': 'string', 'description': 'Optional requested path. Runtime normalizes to current session output/training.jsonl.', }, 'session_num': { 'type': 'integer', 'minimum': 1, 'description': 'Max history queries to include. Defaults to 5.', }, 'session_time_minutes': { 'type': 'integer', 'minimum': 1, 'description': 'Max adjacent history gap in minutes. Defaults to 5.', }, 'context_fields': { 'type': 'array', 'items': {'type': 'string'}, 'description': 'Context fields to inject. Defaults to ["location", "rag"].', }, 'system_prompt': { 'type': 'string', 'description': 'Defaults to 你是小爱同学,中文智能语音助手。', }, 'require_validation_ok': {'type': 'boolean'}, 'overwrite': {'type': 'boolean'}, }, }, handler=resolve_handler(handlers, 'data_agent_export_training_jsonl', 'data-agent'), ), AgentTool( name='data_agent_export_planning_eval_csv', description=( 'Convert canonical data-agent records into evaluation CSV with columns: request_id, newPrompt, ' 'query, 类别真实标签, code标签, complex. The complex column is read from dimensions.complex. ' 'newPrompt wraps the prompt body with <|im_start|>system/user/assistant chat-template tags.' ), parameters={ 'type': 'object', 'properties': { 'records': { 'oneOf': [ {'type': 'array'}, {'type': 'string'}, ], 'description': 'Canonical records as an array, or a JSON string containing the array. Provide exactly one of records or records_path.', }, 'records_path': { 'type': 'string', 'description': 'Path to records JSON/JSONL. Prefer this when records are large. Provide exactly one of records or records_path.', }, 'output_path': { 'type': 'string', 'description': 'Optional requested path. Runtime normalizes to current session output/eval_planning.csv.', }, 'session_num': { 'type': 'integer', 'minimum': 1, 'description': 'Max history queries to include. Defaults to 5.', }, 'session_time_minutes': { 'type': 'integer', 'minimum': 1, 'description': 'Max adjacent history gap in minutes. Defaults to 5.', }, 'context_fields': { 'type': 'array', 'items': {'type': 'string'}, 'description': 'Context fields to inject. Defaults to ["location", "rag"].', }, 'system_prompt': { 'type': 'string', 'description': 'System prompt wrapped into newPrompt chat template. Defaults to 你是小爱同学,中文智能语音助手。', }, 'complex_default': { 'type': 'boolean', 'description': 'Fallback complex value only for legacy records missing dimensions.complex. Defaults to false.', }, 'require_validation_ok': {'type': 'boolean'}, 'overwrite': {'type': 'boolean'}, }, }, handler=resolve_handler(handlers, 'data_agent_export_planning_eval_csv', 'data-agent'), ), ]