Improve data skill routing and tool organization
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
"""工具声明目录。
|
||||
|
||||
每个子模块负责一类工具的名称、描述和 JSON Schema;handler 由运行层注入。
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
from ..agent_tool_core import ToolHandler
|
||||
|
||||
|
||||
def resolve_handler(handlers: Mapping[str, ToolHandler], name: str, group: str) -> ToolHandler:
|
||||
"""从工具组 handler 映射中取出指定工具的执行函数。"""
|
||||
|
||||
try:
|
||||
return handlers[name]
|
||||
except KeyError as exc:
|
||||
raise KeyError(f'Missing {group} tool handler: {name}') from exc
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
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': 'Where draft, records, and validation files should be written.'},
|
||||
'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': '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=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': '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=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.',
|
||||
},
|
||||
'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=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, and labels. '
|
||||
'Generated data requires a confirmed_plan_id from data_agent_confirm_generation_plan.'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'draft_text': {
|
||||
'type': 'string',
|
||||
'description': 'Dataset draft text in dataset draft text v1 format.',
|
||||
},
|
||||
'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.',
|
||||
},
|
||||
},
|
||||
'required': ['draft_text'],
|
||||
},
|
||||
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, 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.',
|
||||
},
|
||||
},
|
||||
'required': ['records'],
|
||||
},
|
||||
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, so the model does not hand-write JSON output.'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'records': {
|
||||
'oneOf': [
|
||||
{'type': 'array'},
|
||||
{'type': 'string'},
|
||||
],
|
||||
'description': 'Canonical records as an array, or a JSON string containing the array.',
|
||||
},
|
||||
'output_path': {
|
||||
'type': 'string',
|
||||
'description': 'Workspace-relative path to write, usually ending in .jsonl.',
|
||||
},
|
||||
'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.',
|
||||
},
|
||||
},
|
||||
'required': ['records', 'output_path'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'data_agent_export_dataset_records', 'data-agent'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
from ..agent_tool_core import AgentTool, ToolHandler
|
||||
from ._builder import resolve_handler
|
||||
|
||||
|
||||
def build_execution_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentTool]:
|
||||
"""构建本地命令、Python 执行与短等待工具声明。"""
|
||||
|
||||
return [
|
||||
AgentTool(
|
||||
name='python_exec',
|
||||
description=(
|
||||
'优先用本工具执行小型 Python 代码或项目内 Python 脚本,用于结构化文件分析、'
|
||||
'JSON/JSONL 处理、批量校验、数据抽样和快速计算。默认使用当前用户独立 Python venv;'
|
||||
'不要用 bash 运行 python/python3/.venv/bin/python。不要用本工具安装依赖。'
|
||||
'缺包时应向用户确认后再处理依赖。一次性分析优先传 code;如需写临时文件,'
|
||||
'必须写入环境变量 PYTHON_EXEC_SCRATCHPAD 指向的会话隔离目录,'
|
||||
'不要在项目根目录创建临时 .py 脚本。'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'code': {
|
||||
'type': 'string',
|
||||
'description': '要通过 python -c 执行的 Python 代码。code 和 script_path 必须二选一;一次性分析优先使用 code。',
|
||||
},
|
||||
'script_path': {
|
||||
'type': 'string',
|
||||
'description': '工作区内已有、需要长期复用的 Python 脚本路径。code 和 script_path 必须二选一;不要为一次性分析在项目根目录新建脚本。',
|
||||
},
|
||||
'args': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': '传给脚本的命令行参数。仅在 script_path 模式下使用。',
|
||||
},
|
||||
'stdin': {
|
||||
'type': 'string',
|
||||
'description': '可选标准输入内容。',
|
||||
},
|
||||
'timeout_seconds': {
|
||||
'type': 'number',
|
||||
'minimum': 1,
|
||||
'description': '可选超时时间,默认使用当前会话命令超时。',
|
||||
},
|
||||
'max_output_chars': {
|
||||
'type': 'integer',
|
||||
'minimum': 100,
|
||||
'description': '可选输出截断长度,默认使用当前会话输出限制。',
|
||||
},
|
||||
},
|
||||
},
|
||||
handler=resolve_handler(handlers, 'python_exec', 'execution'),
|
||||
),
|
||||
AgentTool(
|
||||
name='python_package',
|
||||
description=(
|
||||
'在当前用户独立 Python venv 中检查或安装 Python 包。'
|
||||
'用于 python_exec 缺少 pandas、pyarrow、openpyxl 等分析依赖时;'
|
||||
'不要通过 bash 执行 pip,也不要安装到项目 .venv。'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'action': {
|
||||
'type': 'string',
|
||||
'enum': ['show', 'install'],
|
||||
'description': 'show 查看当前 Python 环境和 pip;install 安装 packages。',
|
||||
},
|
||||
'packages': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': '要安装的包名或 pip requirement spec,例如 pandas、pyarrow==15.0.0。',
|
||||
},
|
||||
'timeout_seconds': {
|
||||
'type': 'number',
|
||||
'minimum': 1,
|
||||
'maximum': 600,
|
||||
'description': '安装超时时间,默认 120 秒。',
|
||||
},
|
||||
'max_output_chars': {
|
||||
'type': 'integer',
|
||||
'minimum': 1000,
|
||||
'maximum': 50000,
|
||||
},
|
||||
},
|
||||
'required': ['action'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'python_package', 'execution'),
|
||||
),
|
||||
AgentTool(
|
||||
name='bash',
|
||||
description=(
|
||||
'运行真实 shell 命令,例如系统命令、进程控制、git 只读检查或用户已确认的依赖安装。'
|
||||
'不要用 bash 执行 python、python3、pip 或 .venv/bin/python;结构化文件分析、'
|
||||
'JSON/JSONL 处理、批量校验、数据抽样和快速计算必须优先使用 python_exec。'
|
||||
'Python 包安装必须优先使用 python_package。'
|
||||
),
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'command': {'type': 'string'},
|
||||
},
|
||||
'required': ['command'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'bash', 'execution'),
|
||||
),
|
||||
AgentTool(
|
||||
name='sleep',
|
||||
description='Pause execution briefly for bounded local wait flows.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'seconds': {'type': 'number', 'minimum': 0.0, 'maximum': 5.0},
|
||||
},
|
||||
'required': ['seconds'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'sleep', 'execution'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
from ..agent_tool_core import AgentTool, ToolHandler
|
||||
from ._builder import resolve_handler
|
||||
|
||||
|
||||
def build_file_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentTool]:
|
||||
"""构建工作区文件读写与文本检索工具声明。"""
|
||||
|
||||
return [
|
||||
AgentTool(
|
||||
name='list_dir',
|
||||
description='List files and directories under a workspace path.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'path': {'type': 'string', 'description': 'Relative path from workspace root.'},
|
||||
'max_entries': {'type': 'integer', 'minimum': 1, 'maximum': 500},
|
||||
},
|
||||
},
|
||||
handler=resolve_handler(handlers, 'list_dir', 'file'),
|
||||
),
|
||||
AgentTool(
|
||||
name='read_file',
|
||||
description='Read the contents of a UTF-8 text file inside the workspace.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'path': {'type': 'string', 'description': 'Relative file path from workspace root.'},
|
||||
'start_line': {'type': 'integer', 'minimum': 1},
|
||||
'end_line': {'type': 'integer', 'minimum': 1},
|
||||
},
|
||||
'required': ['path'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'read_file', 'file'),
|
||||
),
|
||||
AgentTool(
|
||||
name='write_file',
|
||||
description='Write a complete file inside the workspace. Creates parent directories when needed.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'path': {'type': 'string'},
|
||||
'content': {'type': 'string'},
|
||||
},
|
||||
'required': ['path', 'content'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'write_file', 'file'),
|
||||
),
|
||||
AgentTool(
|
||||
name='edit_file',
|
||||
description='Replace text inside a workspace file using exact string matching.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'path': {'type': 'string'},
|
||||
'old_text': {'type': 'string'},
|
||||
'new_text': {'type': 'string'},
|
||||
'replace_all': {'type': 'boolean'},
|
||||
},
|
||||
'required': ['path', 'old_text', 'new_text'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'edit_file', 'file'),
|
||||
),
|
||||
AgentTool(
|
||||
name='notebook_edit',
|
||||
description='Edit a Jupyter notebook cell by replacing or appending source in a .ipynb file.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'path': {'type': 'string'},
|
||||
'cell_index': {'type': 'integer', 'minimum': 0},
|
||||
'source': {'type': 'string'},
|
||||
'cell_type': {'type': 'string'},
|
||||
'create_cell': {'type': 'boolean'},
|
||||
},
|
||||
'required': ['path', 'cell_index', 'source'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'notebook_edit', 'file'),
|
||||
),
|
||||
AgentTool(
|
||||
name='glob_search',
|
||||
description='Find files matching a glob pattern inside the workspace.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'pattern': {'type': 'string'},
|
||||
},
|
||||
'required': ['pattern'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'glob_search', 'file'),
|
||||
),
|
||||
AgentTool(
|
||||
name='grep_search',
|
||||
description='Search for a string or regular expression inside workspace files.',
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'pattern': {'type': 'string'},
|
||||
'path': {'type': 'string'},
|
||||
'literal': {'type': 'boolean'},
|
||||
'max_matches': {'type': 'integer', 'minimum': 1, 'maximum': 500},
|
||||
},
|
||||
'required': ['pattern'],
|
||||
},
|
||||
handler=resolve_handler(handlers, 'grep_search', 'file'),
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user